/*
* 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.doodles.shared;
import android.app.Activity;
import android.app.ActivityManager;
import android.app.Fragment;
import android.content.Context;
import android.os.AsyncTask;
import android.os.Handler;
import android.os.Looper;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.ImageView.ScaleType;
import android.widget.TextView;
import com.google.android.apps.santatracker.doodles.PineappleActivity;
import com.google.android.apps.santatracker.doodles.R;
import com.google.android.apps.santatracker.doodles.shared.PauseView.GamePausedListener;
import com.google.android.apps.santatracker.doodles.shared.PineappleLogEvent.Builder;
import com.google.android.apps.santatracker.doodles.shared.ScoreView.LevelFinishedListener;
import com.google.android.apps.santatracker.doodles.shared.sound.SoundManager;
import com.google.android.apps.santatracker.invites.AppInvitesFragment;
import com.google.android.apps.santatracker.util.FontHelper;
import static com.google.android.apps.santatracker.doodles.shared.EventBus.GAME_LOADED;
import static com.google.android.apps.santatracker.doodles.shared.PineappleLogEvent.DEFAULT_DOODLE_NAME;
import static com.google.android.apps.santatracker.doodles.shared.PineappleLogEvent.LOADING_COMPLETE;
/**
* Base class for Pineapple game fragments.
*/
public abstract class GameFragment extends Fragment implements
GameLoop, ScoreView.OnShareClickedListener {
// Minimum length of the title screen.
public static final long TITLE_DURATION_MS = 1000;
// Require 128MB and if we don't have it, downsample the resources.
private static final int AVAILABLE_MEMORY_REQUIRED = (2 << 27);
// Note this is a context, not an activity. Use getActivity() for an activity.
public final Context context;
public final DoodleConfig doodleConfig;
public final PineappleLogger logger;
protected FrameLayout wrapper;
protected View titleView;
private ImageView titleImageView;
protected ScoreView scoreView;
protected PauseView pauseView;
protected GamePausedListener gamePausedListener = new GamePausedListener() {
@Override
public void onPause() {
onGamePause();
}
@Override
public void onResume() {
onGameResume();
}
@Override
public void onReplay() {
onGameReplay();
}
@Override
public String gameType() {
return getGameType();
}
@Override
public float score() {
return getScore();
}
};
protected LevelFinishedListener levelFinishedListener = new LevelFinishedListener() {
@Override
public void onReplay() {
onGameReplay();
}
@Override
public String gameType() {
return getGameType();
}
@Override
public float score() {
return getScore();
}
@Override
public int shareImageId() {
return getShareImageId();
}
};
protected LogicRefreshThread logicRefreshThread;
protected UIRefreshHandler uiRefreshHandler;
protected SoundManager soundManager;
protected HistoryManager historyManager;
private AsyncTask<Void, Void, Void> asyncLoadGameTask;
protected boolean isFinishedLoading = false;
protected boolean isPaused = false;
protected boolean resumeAfterLoading;
public boolean isDestroyed = false;
public GameFragment() {
this.context = getActivity();
this.doodleConfig = null;
this.logger = new PineappleNullLogger();
}
public GameFragment(Context context, DoodleConfig doodleConfig, PineappleLogger logger) {
this.context = context;
this.doodleConfig = doodleConfig;
this.logger = logger;
}
@Override
public void onResume() {
super.onResume();
if (context == null) {
return;
}
resume();
if ((pauseView == null || pauseView.isPauseButtonEnabled)
&& (scoreView == null || scoreView.getVisibility() != View.VISIBLE)) {
// If we aren't paused or finished, keep the screen on.
AndroidUtils.forceScreenToStayOn(getActivity());
}
if (soundManager != null) {
soundManager.loadMutePreference(context);
}
}
@Override
public void onPause() {
super.onPause();
if (pauseView != null) {
pauseView.pause();
}
resumeAfterLoading = false;
if (uiRefreshHandler != null) {
logicRefreshThread.stopHandler();
uiRefreshHandler.stop();
historyManager.save();
}
if (soundManager != null) {
soundManager.pauseAll();
soundManager.storeMutePreference(context);
}
AndroidUtils.allowScreenToTurnOff(getActivity());
}
// To be overrided by games if they want to do more cleanup on destroy.
protected void onDestroyHelper() {
}
@Override
public void onDestroyView() {
isDestroyed = true;
if (asyncLoadGameTask != null) {
asyncLoadGameTask.cancel(true);
}
EventBus.getInstance().clearListeners();
AnimatedSprite.clearCache();
if (soundManager != null) {
soundManager.releaseAll();
}
onDestroyHelper();
super.onDestroyView();
}
protected void resume() {
if (uiRefreshHandler == null) {
resumeAfterLoading = true;
} else {
logicRefreshThread.startHandler(this);
if (titleView.getVisibility() != View.VISIBLE) {
playMusic();
}
}
}
/**
* Loads the game. Do not override this function. Instead use the three helper functions:
* firstPassLoadOnUiThread, secondPassLoadOnBackgroundThread, finalPassLoadOnUiThread.
*/
public final void loadGame() {
ActivityManager.MemoryInfo mi = new ActivityManager.MemoryInfo();
ActivityManager activityManager = (ActivityManager) getActivity()
.getSystemService(android.content.Context.ACTIVITY_SERVICE);
activityManager.getMemoryInfo(mi);
if (mi.availMem < AVAILABLE_MEMORY_REQUIRED || AnimatedSprite.lastUsedSampleSize > 2) {
// Low available memory, go ahead and load things with a larger sample size.
AnimatedSprite.lastUsedSampleSize = 2;
}
final Handler handler = new Handler(Looper.getMainLooper());
handler.postDelayed(new Runnable() {
@Override
public void run() {
if (context != null && getActivity() != null) {
firstPassLoadOnUiThread();
asyncLoadGameTask = new AsyncTask<Void, Void, Void> () {
@Override
protected Void doInBackground(Void... params) {
if (context != null && getActivity() != null) {
secondPassLoadOnBackgroundThread();
}
return null;
}
@Override
protected void onPostExecute(Void result) {
if (context != null && getActivity() != null) {
finalPassLoadOnUiThread();
}
}
};
asyncLoadGameTask.execute();
}
}
}, 100);
}
protected void firstPassLoadOnUiThread() {
}
protected void secondPassLoadOnBackgroundThread() {
EventBus.getInstance().clearListeners();
}
protected void finalPassLoadOnUiThread() {
}
protected void onFinishedLoading() {
PineappleLogTimer logTimer = PineappleLogTimer.getInstance();
long latencyMs = logTimer.timeElapsedMs();
logger.logEvent(new Builder(DEFAULT_DOODLE_NAME, LOADING_COMPLETE)
.withEventSubType(getGameType())
.withLatencyMs(latencyMs).build());
EventBus.getInstance().sendEvent(GAME_LOADED, latencyMs);
logTimer.reset();
if (pauseView != null) {
pauseView.onFinishedLoading();
}
if (scoreView != null) {
scoreView.setVisibility(View.VISIBLE);
}
isFinishedLoading = true;
}
public boolean isFinishedLoading() {
return isFinishedLoading;
}
protected void startHandlers() {
logicRefreshThread = new LogicRefreshThread();
logicRefreshThread.start();
uiRefreshHandler = new UIRefreshHandler();
// It's annoying when the GC kicks in during gameplay and makes the game stutter. Hint
// that now would be a good time to free up some space before the game starts.
System.gc();
if (resumeAfterLoading) {
resume();
}
}
protected void playMusic() {
soundManager.play(R.raw.fruit_doodle_music);
}
protected void hideTitle() {
UIUtil.fadeOutAndHide(titleView, 1, 500, new Runnable() {
@Override
public void run() {
if (titleImageView != null) {
titleImageView.setImageDrawable(null);
titleImageView = null;
}
}
});
playMusic();
}
public boolean isGamePaused() {
return isPaused;
}
public void onGamePause() {
isPaused = true;
}
protected void onGameResume() {
isPaused = false;
}
protected void onGameReplay() {
replay();
isPaused = false;
PineappleLogTimer.getInstance().reset();
}
protected abstract float getScore();
protected abstract int getShareImageId();
protected boolean onTitleTapped() {
return false;
}
protected void replay() {
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
scoreView.resetToStartState();
}
});
}
protected void loadSounds() {
soundManager.loadLongSound(context, R.raw.fruit_doodle_music, true);
soundManager.loadShortSound(context, R.raw.menu_item_click);
soundManager.loadShortSound(context, R.raw.ui_positive_sound);
}
protected ScoreView getScoreView() {
ScoreView scoreView = new ScoreView(context, this);
scoreView.setDoodleConfig(doodleConfig);
scoreView.setLogger(logger);
scoreView.setListener(levelFinishedListener);
return scoreView;
}
protected PauseView getPauseView() {
PauseView pauseView = new PauseView(context);
pauseView.setDoodleConfig(doodleConfig);
pauseView.setLogger(logger);
pauseView.setListener(gamePausedListener);
return pauseView;
}
protected View getTitleView(int resId, int textId) {
FrameLayout titleLayout = new FrameLayout(context);
titleImageView = new ImageView(context) {
@Override
public boolean onTouchEvent(MotionEvent event) {
return onTitleTapped();
}
};
titleImageView.setImageResource(resId);
titleImageView.setScaleType(ScaleType.CENTER_CROP);
FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT);
titleLayout.addView(titleImageView, lp);
// Add fun fact.
boolean defaultEnabledValue = doodleConfig == null || doodleConfig.extraData == null;
// Check if we are english, or we are allowed to run in all languages.
if (textId != 0) {
LayoutInflater inflater = LayoutInflater.from(context);
TextView textView = (TextView) inflater.inflate(R.layout.fact_view, titleLayout, false);
// Set text and font
textView.setText(AndroidUtils.getText(context.getResources(), textId));
FontHelper.makeLobster(textView);
lp = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.WRAP_CONTENT, Gravity.BOTTOM);
titleLayout.addView(textView, lp);
}
return titleLayout;
}
@Override
public void onShareClicked() {
Activity activity = getActivity();
if (activity instanceof PineappleActivity) {
AppInvitesFragment invites = ((PineappleActivity) activity).getAppInvitesFragment();
if (invites != null) {
invites.sendGenericInvite();
}
}
}
protected abstract String getGameType();
public void onBackPressed() {
pauseView.pause();
}
public abstract boolean isGameOver();
}