Skip to content

Instantly share code, notes, and snippets.

@stevetranby
Last active October 25, 2019 17:58
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save stevetranby/3151c541a7ce17cbd9bc to your computer and use it in GitHub Desktop.
Save stevetranby/3151c541a7ce17cbd9bc to your computer and use it in GitHub Desktop.
A collection of files modified to support .obb extension files. Hopefully this is everything. The changes were as follows.
/****************************************************************************
Copyright (c) 2010-2013 cocos2d-x.org
http://www.cocos2d-x.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
#include "CCFileUtils.h"
#include "CCDirector.h"
#include "cocoa/CCDictionary.h"
#include "cocoa/CCString.h"
#include "cocoa/CCBool.h"
#include "cocoa/CCFloat.h"
#include "cocoa/CCDouble.h"
#include "cocoa/CCInteger.h"
#include "CCSAXParser.h"
#include "support/tinyxml2/tinyxml2.h"
#include "support/zip_support/unzip.h"
#include <stack>
#include <sstream>
#include <iomanip>
using namespace std;
#if (CC_TARGET_PLATFORM != CC_PLATFORM_IOS) && (CC_TARGET_PLATFORM != CC_PLATFORM_MAC)
NS_CC_BEGIN
typedef enum
{
SAX_NONE = 0,
SAX_KEY,
SAX_DICT,
SAX_INT,
SAX_REAL,
SAX_STRING,
SAX_ARRAY
}CCSAXState;
typedef enum
{
SAX_RESULT_NONE = 0,
SAX_RESULT_DICT,
SAX_RESULT_ARRAY
}CCSAXResult;
std::string Convert (int number)
{
std::ostringstream buff;
buff<<number;
return buff.str();
}
std::string Convert (float number)
{
std::ostringstream buff;
buff<<number;
return buff.str();
}
std::string Convert (double number)
{
std::ostringstream buff;
buff << std::fixed << number;
return buff.str();
}
class CCDictMaker : public CCSAXDelegator
{
public:
CCSAXResult m_eResultType;
CCArray* m_pRootArray;
CCDictionary *m_pRootDict;
CCDictionary *m_pCurDict;
std::stack<CCDictionary*> m_tDictStack;
std::string m_sCurKey; ///< parsed key
std::string m_sCurValue; // parsed value
CCSAXState m_tState;
CCArray* m_pArray;
std::stack<CCArray*> m_tArrayStack;
std::stack<CCSAXState> m_tStateStack;
public:
CCDictMaker()
: m_eResultType(SAX_RESULT_NONE),
m_pRootArray(NULL),
m_pRootDict(NULL),
m_pCurDict(NULL),
m_tState(SAX_NONE),
m_pArray(NULL)
{
}
~CCDictMaker()
{
}
CCDictionary* dictionaryWithContentsOfFile(const char *pFileName)
{
m_eResultType = SAX_RESULT_DICT;
CCSAXParser parser;
if (false == parser.init("UTF-8"))
{
return NULL;
}
parser.setDelegator(this);
parser.parse(pFileName);
return m_pRootDict;
}
CCArray* arrayWithContentsOfFile(const char* pFileName)
{
m_eResultType = SAX_RESULT_ARRAY;
CCSAXParser parser;
if (false == parser.init("UTF-8"))
{
return NULL;
}
parser.setDelegator(this);
parser.parse(pFileName);
return m_pArray;
}
void startElement(void *ctx, const char *name, const char **atts)
{
CC_UNUSED_PARAM(ctx);
CC_UNUSED_PARAM(atts);
std::string sName((char*)name);
if( sName == "dict" )
{
m_pCurDict = new CCDictionary();
if(m_eResultType == SAX_RESULT_DICT && m_pRootDict == NULL)
{
// Because it will call m_pCurDict->release() later, so retain here.
m_pRootDict = m_pCurDict;
m_pRootDict->retain();
}
m_tState = SAX_DICT;
CCSAXState preState = SAX_NONE;
if (! m_tStateStack.empty())
{
preState = m_tStateStack.top();
}
if (SAX_ARRAY == preState)
{
// add the dictionary into the array
m_pArray->addObject(m_pCurDict);
}
else if (SAX_DICT == preState)
{
// add the dictionary into the pre dictionary
CCAssert(! m_tDictStack.empty(), "The state is wrong!");
CCDictionary* pPreDict = m_tDictStack.top();
pPreDict->setObject(m_pCurDict, m_sCurKey.c_str());
}
m_pCurDict->release();
// record the dict state
m_tStateStack.push(m_tState);
m_tDictStack.push(m_pCurDict);
}
else if(sName == "key")
{
m_tState = SAX_KEY;
}
else if(sName == "integer")
{
m_tState = SAX_INT;
}
else if(sName == "real")
{
m_tState = SAX_REAL;
}
else if(sName == "string")
{
m_tState = SAX_STRING;
}
else if (sName == "array")
{
m_tState = SAX_ARRAY;
m_pArray = new CCArray();
if (m_eResultType == SAX_RESULT_ARRAY && m_pRootArray == NULL)
{
m_pRootArray = m_pArray;
m_pRootArray->retain();
}
CCSAXState preState = SAX_NONE;
if (! m_tStateStack.empty())
{
preState = m_tStateStack.top();
}
if (preState == SAX_DICT)
{
m_pCurDict->setObject(m_pArray, m_sCurKey.c_str());
}
else if (preState == SAX_ARRAY)
{
CCAssert(! m_tArrayStack.empty(), "The state is wrong!");
CCArray* pPreArray = m_tArrayStack.top();
pPreArray->addObject(m_pArray);
}
m_pArray->release();
// record the array state
m_tStateStack.push(m_tState);
m_tArrayStack.push(m_pArray);
}
else
{
m_tState = SAX_NONE;
}
}
void endElement(void *ctx, const char *name)
{
CC_UNUSED_PARAM(ctx);
CCSAXState curState = m_tStateStack.empty() ? SAX_DICT : m_tStateStack.top();
std::string sName((char*)name);
if( sName == "dict" )
{
m_tStateStack.pop();
m_tDictStack.pop();
if ( !m_tDictStack.empty())
{
m_pCurDict = m_tDictStack.top();
}
}
else if (sName == "array")
{
m_tStateStack.pop();
m_tArrayStack.pop();
if (! m_tArrayStack.empty())
{
m_pArray = m_tArrayStack.top();
}
}
else if (sName == "true")
{
CCString *str = new CCString("1");
if (SAX_ARRAY == curState)
{
m_pArray->addObject(str);
}
else if (SAX_DICT == curState)
{
m_pCurDict->setObject(str, m_sCurKey.c_str());
}
str->release();
}
else if (sName == "false")
{
CCString *str = new CCString("0");
if (SAX_ARRAY == curState)
{
m_pArray->addObject(str);
}
else if (SAX_DICT == curState)
{
m_pCurDict->setObject(str, m_sCurKey.c_str());
}
str->release();
}
else if (sName == "string" || sName == "integer" || sName == "real")
{
CCString* pStrValue = new CCString(m_sCurValue);
//CCLOG("strval = %s", pStrValue->getCString());
if (SAX_ARRAY == curState)
{
m_pArray->addObject(pStrValue);
}
else if (SAX_DICT == curState)
{
m_pCurDict->setObject(pStrValue, m_sCurKey.c_str());
}
pStrValue->release();
m_sCurValue.clear();
}
m_tState = SAX_NONE;
}
void textHandler(void *ctx, const char *ch, int len)
{
CC_UNUSED_PARAM(ctx);
if (m_tState == SAX_NONE)
{
return;
}
CCSAXState curState = m_tStateStack.empty() ? SAX_DICT : m_tStateStack.top();
CCString *pText = new CCString(std::string((char*)ch,0,len));
switch(m_tState)
{
case SAX_KEY:
m_sCurKey = pText->getCString();
break;
case SAX_INT:
case SAX_REAL:
case SAX_STRING:
{
if (curState == SAX_DICT)
{
CCAssert(!m_sCurKey.empty(), "key not found : <integer/real>");
}
m_sCurValue.append(pText->getCString());
}
break;
default:
break;
}
pText->release();
}
};
CCDictionary* CCFileUtils::createCCDictionaryWithContentsOfFile(const std::string& filename)
{
std::string fullPath = fullPathForFilename(filename.c_str());
CCDictMaker tMaker;
return tMaker.dictionaryWithContentsOfFile(fullPath.c_str());
}
CCArray* CCFileUtils::createCCArrayWithContentsOfFile(const std::string& filename)
{
std::string fullPath = fullPathForFilename(filename.c_str());
CCDictMaker tMaker;
return tMaker.arrayWithContentsOfFile(fullPath.c_str());
}
/*
* forward statement
*/
static tinyxml2::XMLElement* generateElementForArray(cocos2d::CCArray *array, tinyxml2::XMLDocument *pDoc);
static tinyxml2::XMLElement* generateElementForDict(cocos2d::CCDictionary *dict, tinyxml2::XMLDocument *pDoc);
/*
* Use tinyxml2 to write plist files
*/
bool CCFileUtils::writeToFile(cocos2d::CCDictionary *dict, const std::string &fullPath)
{
//CCLOG("tinyxml2 CCDictionary %d writeToFile %s", dict->m_uID, fullPath.c_str());
tinyxml2::XMLDocument *pDoc = new tinyxml2::XMLDocument();
if (NULL == pDoc)
return false;
tinyxml2::XMLDeclaration *pDeclaration = pDoc->NewDeclaration("xml version=\"1.0\" encoding=\"UTF-8\"");
if (NULL == pDeclaration)
{
delete pDoc;
return false;
}
pDoc->LinkEndChild(pDeclaration);
tinyxml2::XMLElement *docType = pDoc->NewElement("!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\"");
pDoc->LinkEndChild(docType);
tinyxml2::XMLElement *pRootEle = pDoc->NewElement("plist");
pRootEle->SetAttribute("version", "1.0");
if (NULL == pRootEle)
{
delete pDoc;
return false;
}
pDoc->LinkEndChild(pRootEle);
tinyxml2::XMLElement *innerDict = generateElementForDict(dict, pDoc);
if (NULL == innerDict )
{
delete pDoc;
return false;
}
pRootEle->LinkEndChild(innerDict);
tinyxml2::XMLError err = pDoc->SaveFile(fullPath.c_str());
CCLOG("err = %d", err);
bool bRet = tinyxml2::XML_SUCCESS == err;
delete pDoc;
return bRet;
}
/*
* Generate tinyxml2::XMLElement for CCObject through a tinyxml2::XMLDocument
*/
static tinyxml2::XMLElement* generateElementForObject(cocos2d::CCObject *object, tinyxml2::XMLDocument *pDoc)
{
// object is CCString
if (cocos2d::CCString *str = dynamic_cast<CCString *>(object))
{
tinyxml2::XMLElement* node = pDoc->NewElement("string");
tinyxml2::XMLText* content = pDoc->NewText(str->getCString());
node->LinkEndChild(content);
return node;
}
// the object is bool
if (cocos2d::CCBool *element = dynamic_cast<cocos2d::CCBool *>(object)) {
tinyxml2::XMLElement* node = pDoc->NewElement("false");
if(true == element->getValue())
node = pDoc->NewElement("true");
return node;
}
// the object is float
if (cocos2d::CCFloat *element = dynamic_cast<cocos2d::CCFloat *>(object)) {
std::string str = Convert(element->getValue());
tinyxml2::XMLElement* node = pDoc->NewElement("real");
tinyxml2::XMLText* content = pDoc->NewText(str.c_str());
node->LinkEndChild(content);
return node;
}
// the object is double
if (cocos2d::CCDouble *element = dynamic_cast<cocos2d::CCDouble *>(object)) {
std::string str = Convert(element->getValue());
tinyxml2::XMLElement* node = pDoc->NewElement("real");
tinyxml2::XMLText* content = pDoc->NewText(str.c_str());
node->LinkEndChild(content);
return node;
}
// the object is int
if (cocos2d::CCInteger *element = dynamic_cast<cocos2d::CCInteger *>(object)) {
std::string str = Convert(element->getValue());
tinyxml2::XMLElement* node = pDoc->NewElement("integer");
tinyxml2::XMLText* content = pDoc->NewText(str.c_str());
node->LinkEndChild(content);
return node;
}
// object is CCArray
if (CCArray *array = dynamic_cast<CCArray *>(object))
return generateElementForArray(array, pDoc);
// object is CCDictionary
if (CCDictionary *innerDict = dynamic_cast<CCDictionary *>(object))
return generateElementForDict(innerDict, pDoc);
CCLOG("This type cannot appear in property list");
return NULL;
}
/*
* Generate tinyxml2::XMLElement for CCDictionary through a tinyxml2::XMLDocument
*/
static tinyxml2::XMLElement* generateElementForDict(cocos2d::CCDictionary *dict, tinyxml2::XMLDocument *pDoc)
{
tinyxml2::XMLElement* rootNode = pDoc->NewElement("dict");
CCDictElement *dictElement = NULL;
CCDICT_FOREACH(dict, dictElement)
{
tinyxml2::XMLElement* tmpNode = pDoc->NewElement("key");
rootNode->LinkEndChild(tmpNode);
tinyxml2::XMLText* content = pDoc->NewText(dictElement->getStrKey());
tmpNode->LinkEndChild(content);
CCObject *object = dictElement->getObject();
tinyxml2::XMLElement *element = generateElementForObject(object, pDoc);
if (element)
rootNode->LinkEndChild(element);
}
return rootNode;
}
/*
* Generate tinyxml2::XMLElement for CCArray through a tinyxml2::XMLDocument
*/
static tinyxml2::XMLElement* generateElementForArray(cocos2d::CCArray *array, tinyxml2::XMLDocument *pDoc)
{
tinyxml2::XMLElement* rootNode = pDoc->NewElement("array");
CCObject *object = NULL;
CCARRAY_FOREACH(array, object)
{
tinyxml2::XMLElement *element = generateElementForObject(object, pDoc);
if (element)
rootNode->LinkEndChild(element);
}
return rootNode;
}
#else
NS_CC_BEGIN
/* The subclass CCFileUtilsIOS and CCFileUtilsMac should override these two method. */
CCDictionary* CCFileUtils::createCCDictionaryWithContentsOfFile(const std::string& filename) {return NULL;}
bool CCFileUtils::writeToFile(cocos2d::CCDictionary *dict, const std::string &fullPath) {return NULL;}
CCArray* CCFileUtils::createCCArrayWithContentsOfFile(const std::string& filename) {return NULL;}
#endif /* (CC_TARGET_PLATFORM != CC_PLATFORM_IOS) && (CC_TARGET_PLATFORM != CC_PLATFORM_MAC) */
CCFileUtils* CCFileUtils::s_sharedFileUtils = NULL;
void CCFileUtils::purgeFileUtils()
{
CC_SAFE_DELETE(s_sharedFileUtils);
}
CCFileUtils::CCFileUtils()
: m_pFilenameLookupDict(NULL)
{
}
CCFileUtils::~CCFileUtils()
{
CC_SAFE_RELEASE(m_pFilenameLookupDict);
}
bool CCFileUtils::init()
{
m_searchPathArray.push_back(m_strDefaultResRootPath);
m_searchResolutionsOrderArray.push_back("");
return true;
}
void CCFileUtils::purgeCachedEntries()
{
m_fullPathCache.clear();
}
unsigned char* CCFileUtils::getFileData(const char* pszFileName, const char* pszMode, unsigned long * pSize)
{
unsigned char * pBuffer = NULL;
CCAssert(pszFileName != NULL && pSize != NULL && pszMode != NULL, "Invalid parameters.");
*pSize = 0;
do
{
// read the file from hardware
std::string fullPath = fullPathForFilename(pszFileName);
FILE *fp = fopen(fullPath.c_str(), pszMode);
CC_BREAK_IF(!fp);
fseek(fp,0,SEEK_END);
*pSize = ftell(fp);
fseek(fp,0,SEEK_SET);
fflush(fp);
pBuffer = new unsigned char[*pSize];
*pSize = fread(pBuffer,sizeof(unsigned char), *pSize,fp);
fclose(fp);
} while (0);
if (! pBuffer)
{
std::string msg = "Get data from file(";
msg.append(pszFileName).append(") failed!");
CCLOG("%s", msg.c_str());
}
return pBuffer;
}
unsigned char* CCFileUtils::getFileDataFromZip(const char* pszZipFilePath, const char* pszFileName, unsigned long * pSize)
{
unsigned char * pBuffer = NULL;
unzFile pFile = NULL;
*pSize = 0;
do
{
CC_BREAK_IF(!pszZipFilePath || !pszFileName);
CC_BREAK_IF(strlen(pszZipFilePath) == 0);
pFile = unzOpen(pszZipFilePath);
CC_BREAK_IF(!pFile);
int nRet = unzLocateFile(pFile, pszFileName, 1);
CC_BREAK_IF(UNZ_OK != nRet);
char szFilePathA[260];
unz_file_info FileInfo;
nRet = unzGetCurrentFileInfo(pFile, &FileInfo, szFilePathA, sizeof(szFilePathA), NULL, 0, NULL, 0);
CC_BREAK_IF(UNZ_OK != nRet);
nRet = unzOpenCurrentFile(pFile);
CC_BREAK_IF(UNZ_OK != nRet);
pBuffer = new unsigned char[FileInfo.uncompressed_size];
int CC_UNUSED nSize = unzReadCurrentFile(pFile, pBuffer, FileInfo.uncompressed_size);
CCAssert(nSize == 0 || nSize == (int)FileInfo.uncompressed_size, "the file size is wrong");
*pSize = FileInfo.uncompressed_size;
unzCloseCurrentFile(pFile);
} while (0);
if (pFile)
{
unzClose(pFile);
}
return pBuffer;
}
std::string CCFileUtils::getNewFilename(const char* pszFileName)
{
const char* pszNewFileName = NULL;
// in Lookup Filename dictionary ?
CCString* fileNameFound = m_pFilenameLookupDict ? (CCString*)m_pFilenameLookupDict->objectForKey(pszFileName) : NULL;
if( NULL == fileNameFound || fileNameFound->length() == 0) {
pszNewFileName = pszFileName;
}
else {
pszNewFileName = fileNameFound->getCString();
//CCLOG("FOUND NEW FILE NAME: %s.", pszNewFileName);
}
return pszNewFileName;
}
std::string CCFileUtils::getPathForFilename(const std::string& filename, const std::string& resolutionDirectory, const std::string& searchPath)
{
std::string file = filename;
std::string file_path = "";
size_t pos = filename.find_last_of("/");
if (pos != std::string::npos)
{
file_path = filename.substr(0, pos+1);
file = filename.substr(pos+1);
}
// searchPath + file_path + resourceDirectory
std::string path = searchPath;
path += file_path;
path += resolutionDirectory;
path = getFullPathForDirectoryAndFilename(path, file);
//CCLOG("getPathForFilename, fullPath = %s", path.c_str());
return path;
}
std::string CCFileUtils::fullPathForFilename(const char* pszFileName)
{
CCAssert(pszFileName != NULL, "CCFileUtils: Invalid path");
std::string strFileName = pszFileName;
if (isAbsolutePath(pszFileName))
{
//CCLOG("Return absolute path( %s ) directly.", pszFileName);
return pszFileName;
}
// Already Cached ?
std::map<std::string, std::string>::iterator cacheIter = m_fullPathCache.find(pszFileName);
if (cacheIter != m_fullPathCache.end())
{
//CCLOG("Return full path from cache: %s", cacheIter->second.c_str());
return cacheIter->second;
}
// Get the new file name.
std::string newFilename = getNewFilename(pszFileName);
string fullpath = "";
for (std::vector<std::string>::iterator searchPathsIter = m_searchPathArray.begin(); searchPathsIter != m_searchPathArray.end(); ++searchPathsIter)
{
for (std::vector<std::string>::iterator resOrderIter = m_searchResolutionsOrderArray.begin(); resOrderIter != m_searchResolutionsOrderArray.end(); ++resOrderIter)
{
//CCLOG("\n\nSEARCHING: %s, %s, %s", newFilename.c_str(), resOrderIter->c_str(), searchPathsIter->c_str());
fullpath = this->getPathForFilename(newFilename, *resOrderIter, *searchPathsIter);
if (fullpath.length() > 0)
{
// Using the filename passed in as key.
m_fullPathCache.insert(std::pair<std::string, std::string>(pszFileName, fullpath));
//CCLOG("Returning path: %s", fullpath.c_str());
return fullpath;
}
}
}
//CCLOG("cocos2d: fullPathForFilename: No file found at %s. Possible missing file.", pszFileName);
// The file wasn't found, return the file name passed in.
return pszFileName;
}
const char* CCFileUtils::fullPathFromRelativeFile(const char *pszFilename, const char *pszRelativeFile)
{
std::string relativeFile = pszRelativeFile;
CCString *pRet = CCString::create("");
pRet->m_sString = relativeFile.substr(0, relativeFile.rfind('/')+1);
pRet->m_sString += getNewFilename(pszFilename);
return pRet->getCString();
}
void CCFileUtils::setSearchResolutionsOrder(const std::vector<std::string>& searchResolutionsOrder)
{
bool bExistDefault = false;
m_fullPathCache.clear();
m_searchResolutionsOrderArray.clear();
for (std::vector<std::string>::const_iterator iter = searchResolutionsOrder.begin(); iter != searchResolutionsOrder.end(); ++iter)
{
std::string resolutionDirectory = *iter;
if (!bExistDefault && resolutionDirectory == "")
{
bExistDefault = true;
}
if (resolutionDirectory.length() > 0 && resolutionDirectory[resolutionDirectory.length()-1] != '/')
{
resolutionDirectory += "/";
}
m_searchResolutionsOrderArray.push_back(resolutionDirectory);
}
if (!bExistDefault)
{
m_searchResolutionsOrderArray.push_back("");
}
}
void CCFileUtils::addSearchResolutionsOrder(const char* order)
{
m_searchResolutionsOrderArray.push_back(order);
}
const std::vector<std::string>& CCFileUtils::getSearchResolutionsOrder()
{
return m_searchResolutionsOrderArray;
}
const std::vector<std::string>& CCFileUtils::getSearchPaths()
{
return m_searchPathArray;
}
void CCFileUtils::setSearchPaths(const std::vector<std::string>& searchPaths)
{
bool bExistDefaultRootPath = false;
m_fullPathCache.clear();
m_searchPathArray.clear();
for (std::vector<std::string>::const_iterator iter = searchPaths.begin(); iter != searchPaths.end(); ++iter)
{
std::string strPrefix;
std::string path;
if (!isAbsolutePath(*iter))
{ // Not an absolute path
strPrefix = m_strDefaultResRootPath;
}
path = strPrefix+(*iter);
if (path.length() > 0 && path[path.length()-1] != '/')
{
path += "/";
}
if (!bExistDefaultRootPath && path == m_strDefaultResRootPath)
{
bExistDefaultRootPath = true;
}
m_searchPathArray.push_back(path);
}
if (!bExistDefaultRootPath)
{
//CCLOG("Default root path doesn't exist, adding it.");
m_searchPathArray.push_back(m_strDefaultResRootPath);
}
}
void CCFileUtils::addSearchPath(const char* path_)
{
std::string strPrefix;
std::string path(path_);
if (!isAbsolutePath(path))
{ // Not an absolute path
strPrefix = m_strDefaultResRootPath;
}
path = strPrefix + path;
if (path.length() > 0 && path[path.length()-1] != '/')
{
path += "/";
}
m_searchPathArray.push_back(path);
}
void CCFileUtils::removeSearchPath(const char *path_)
{
std::string strPrefix;
std::string path(path_);
if (!isAbsolutePath(path))
{ // Not an absolute path
strPrefix = m_strDefaultResRootPath;
}
path = strPrefix + path;
if (path.length() > 0 && path[path.length()-1] != '/')
{
path += "/";
}
std::vector<std::string>::iterator iter = std::find(m_searchPathArray.begin(), m_searchPathArray.end(), path);
m_searchPathArray.erase(iter);
}
void CCFileUtils::removeAllPaths()
{
m_searchPathArray.clear();
}
void CCFileUtils::setFilenameLookupDictionary(CCDictionary* pFilenameLookupDict)
{
m_fullPathCache.clear();
CC_SAFE_RELEASE(m_pFilenameLookupDict);
m_pFilenameLookupDict = pFilenameLookupDict;
CC_SAFE_RETAIN(m_pFilenameLookupDict);
}
void CCFileUtils::loadFilenameLookupDictionaryFromFile(const char* filename)
{
std::string fullPath = this->fullPathForFilename(filename);
if (fullPath.length() > 0)
{
CCDictionary* pDict = CCDictionary::createWithContentsOfFile(fullPath.c_str());
if (pDict)
{
CCDictionary* pMetadata = (CCDictionary*)pDict->objectForKey("metadata");
int version = ((CCString*)pMetadata->objectForKey("version"))->intValue();
if (version != 1)
{
CCLOG("cocos2d: ERROR: Invalid filenameLookup dictionary version: %ld. Filename: %s", (long)version, filename);
return;
}
setFilenameLookupDictionary((CCDictionary*)pDict->objectForKey("filenames"));
}
}
}
std::string CCFileUtils::getFullPathForDirectoryAndFilename(const std::string& strDirectory, const std::string& strFilename)
{
std::string ret = strDirectory+strFilename;
if (!isFileExist(ret)) {
ret = "";
}
return ret;
}
bool CCFileUtils::isAbsolutePath(const std::string& strPath)
{
return strPath[0] == '/' ? true : false;
}
//////////////////////////////////////////////////////////////////////////
// Notification support when getFileData from invalid file path.
//////////////////////////////////////////////////////////////////////////
static bool s_bPopupNotify = true;
void CCFileUtils::setPopupNotify(bool bNotify)
{
s_bPopupNotify = bNotify;
}
bool CCFileUtils::isPopupNotify()
{
return s_bPopupNotify;
}
NS_CC_END
/****************************************************************************
Copyright (c) 2010 cocos2d-x.org
http://www.cocos2d-x.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
#include "CCFileUtilsAndroid.h"
#include "support/zip_support/ZipUtils.h"
#include "platform/CCCommon.h"
#include "jni/Java_org_cocos2dx_lib_Cocos2dxHelper.h"
using namespace std;
NS_CC_BEGIN
// record the zip on the resource path
static ZipFile *s_pZipFile = NULL;
CCFileUtils* CCFileUtils::sharedFileUtils()
{
if (s_sharedFileUtils == NULL)
{
s_sharedFileUtils = new CCFileUtilsAndroid();
s_sharedFileUtils->init();
std::string resourcePath = getApkPath();
CCLOG("resourcePath = %s", resourcePath.c_str());
s_pZipFile = new ZipFile(resourcePath, "assets/");
}
return s_sharedFileUtils;
}
CCFileUtilsAndroid::CCFileUtilsAndroid()
{
}
CCFileUtilsAndroid::~CCFileUtilsAndroid()
{
CC_SAFE_DELETE(s_pZipFile);
}
bool CCFileUtilsAndroid::init()
{
m_strDefaultResRootPath = "assets/";
return CCFileUtils::init();
}
bool CCFileUtilsAndroid::isFileExist(const std::string& strFilePath)
{
if (0 == strFilePath.length())
{
return false;
}
bool bFound = false;
// Check whether file exists in apk.
if (strFilePath[0] != '/')
{
std::string strPath = strFilePath;
if (strPath.find(m_strDefaultResRootPath) != 0)
{
// Didn't find "assets/" at the beginning of the path, adding it.
strPath.insert(0, m_strDefaultResRootPath);
}
if (s_pZipFile->fileExists(strPath))
{
bFound = true;
}
}
else
{
FILE *fp = fopen(strFilePath.c_str(), "r");
if(fp)
{
bFound = true;
fclose(fp);
}
}
return bFound;
}
bool CCFileUtilsAndroid::isAbsolutePath(const std::string& strPath)
{
// On Android, there are two situations for full path.
// 1) Files in APK, e.g. assets/path/path/file.png
// 2) Files not in APK, e.g. /data/data/org.cocos2dx.hellocpp/cache/path/path/file.png, or /sdcard/path/path/file.png.
// So these two situations need to be checked on Android.
if (strPath[0] == '/' || strPath.find(m_strDefaultResRootPath) == 0)
{
return true;
}
return false;
}
unsigned char* CCFileUtilsAndroid::getFileData(const char* pszFileName, const char* pszMode, unsigned long * pSize)
{
return doGetFileData(pszFileName, pszMode, pSize, false);
}
unsigned char* CCFileUtilsAndroid::getFileDataForAsync(const char* pszFileName, const char* pszMode, unsigned long * pSize)
{
return doGetFileData(pszFileName, pszMode, pSize, true);
}
unsigned char* CCFileUtilsAndroid::doGetFileData(const char* pszFileName, const char* pszMode, unsigned long * pSize, bool forAsync)
{
unsigned char * pData = 0;
if ((! pszFileName) || (! pszMode) || 0 == strlen(pszFileName))
{
return 0;
}
string fullPath = fullPathForFilename(pszFileName);
if ( fullPath.find( "Android/obb/" ) != std::string::npos )
{
// Read from expansion
CCLOG("GETTING FILE RELATIVE ZIP: %s", fullPath.c_str());
if (forAsync)
{
pData = s_pZipFile->getFileData(fullPath.c_str(), pSize, s_pZipFile->_dataThread);
}
else
{
pData = s_pZipFile->getFileData(fullPath.c_str(), pSize);
}
}
else if (pszFileName[0] != '/')
{
if (forAsync)
{
pData = s_pZipFile->getFileData(fullPath.c_str(), pSize, s_pZipFile->_dataThread);
}
else
{
pData = s_pZipFile->getFileData(fullPath.c_str(), pSize);
}
}
else
{
do
{
// read rrom other path than user set it
CCLOG("GETTING FILE ABSOLUTE DATA: %s", pszFileName);
FILE *fp = fopen(fullPath.c_str(), pszMode);
CC_BREAK_IF(!fp);
unsigned long size;
fseek(fp,0,SEEK_END);
size = ftell(fp);
fseek(fp,0,SEEK_SET);
pData = new unsigned char[size];
size = fread(pData,sizeof(unsigned char), size,fp);
fclose(fp);
if (pSize)
{
*pSize = size;
}
} while (0);
}
if (! pData)
{
std::string msg = "Get data from file(";
msg.append(pszFileName).append(") failed!");
CCLOG("%s", msg.c_str());
}
return pData;
}
string CCFileUtilsAndroid::getWritablePath()
{
// Fix for Nexus 10 (Android 4.2 multi-user environment)
// the path is retrieved through Java Context.getCacheDir() method
string dir("");
string tmp = getFileDirectoryJNI();
if (tmp.length() > 0)
{
dir.append(tmp).append("/");
return dir;
}
else
{
return "";
}
}
NS_CC_END
/****************************************************************************
Copyright (c) 2010-2013 cocos2d-x.org
http://www.cocos2d-x.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
package org.cocos2dx.lib;
import org.cocos2dx.lib.Cocos2dxHelper.Cocos2dxHelperListener;
import android.app.Activity;
import android.content.Context;
import android.os.Build;
import android.os.Bundle;
import android.os.Message;
import android.view.ViewGroup;
import android.util.Log;
import android.widget.FrameLayout;
public abstract class Cocos2dxActivity extends Activity implements
Cocos2dxHelperListener {
// ===========================================================
// Constants
// ===========================================================
private static final String TAG = Cocos2dxActivity.class.getSimpleName();
// ===========================================================
// Fields
// ===========================================================
private Cocos2dxGLSurfaceView mGLSurfaceView;
private Cocos2dxHandler mHandler = null;
private boolean useExpanisonFiles = false;
private static Context sContext = null;
public static Context getContext() {
return sContext;
}
// ===========================================================
// Constructors
// ===========================================================
@Override
protected void onCreate(final Bundle savedInstanceState) {
this.onCreate(savedInstanceState, false);
}
protected void onCreate(final Bundle savedInstanceState, boolean useExpansionFiles) {
super.onCreate(savedInstanceState);
sContext = this;
this.useExpanisonFiles = useExpansionFiles;
// this.mHandler = new Cocos2dxHandler(this);
//
// Cocos2dxHelper.init(this, this, this.useExpanisonFiles);
//
// Log.d(TAG, "package name= " + Cocos2dxHelper.getCocos2dxPackageName());
// Log.d(TAG, "apkPath = " + Cocos2dxHelper.getCocos2dxsNativeApkPath());
// Log.d(TAG, "writepath = " + Cocos2dxHelper.getCocos2dxWritablePath());
//
// this.init();
}
public void delayedInit(boolean useExpansionFiles) {
this.mHandler = new Cocos2dxHandler(this);
this.useExpanisonFiles = useExpansionFiles;
Cocos2dxHelper.init(this, this, this.useExpanisonFiles);
Log.d(TAG, "package name= " + Cocos2dxHelper.getCocos2dxPackageName());
Log.d(TAG, "apkPath = " + Cocos2dxHelper.getCocos2dxsNativeApkPath());
Log.d(TAG, "writepath = " + Cocos2dxHelper.getCocos2dxWritablePath());
this.init();
}
// ===========================================================
// Getter & Setter
// ===========================================================
// ===========================================================
// Methods for/from SuperClass/Interfaces
// ===========================================================
@Override
protected void onResume() {
super.onResume();
if (this.mHandler != null) {
Cocos2dxHelper.onResume();
this.mGLSurfaceView.onResume();
}
}
@Override
protected void onPause() {
super.onPause();
if (this.mHandler != null) {
Cocos2dxHelper.onPause();
this.mGLSurfaceView.onPause();
}
}
@Override
public void showDialog(final String pTitle, final String pMessage) {
Message msg = new Message();
msg.what = Cocos2dxHandler.HANDLER_SHOW_DIALOG;
msg.obj = new Cocos2dxHandler.DialogMessage(pTitle, pMessage);
this.mHandler.sendMessage(msg);
}
@Override
public void showEditTextDialog(final String pTitle, final String pContent,
final int pInputMode, final int pInputFlag, final int pReturnType,
final int pMaxLength) {
Message msg = new Message();
msg.what = Cocos2dxHandler.HANDLER_SHOW_EDITBOX_DIALOG;
msg.obj = new Cocos2dxHandler.EditBoxMessage(pTitle, pContent,
pInputMode, pInputFlag, pReturnType, pMaxLength);
this.mHandler.sendMessage(msg);
}
@Override
public void extraLog(final String pMessage) {
// do nothing, but subclass could implement something
}
@Override
public void runOnGLThread(final Runnable pRunnable) {
this.mGLSurfaceView.queueEvent(pRunnable);
}
// ===========================================================
// Methods
// ===========================================================
public void init() {
// FrameLayout
ViewGroup.LayoutParams framelayout_params = new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.FILL_PARENT,
ViewGroup.LayoutParams.FILL_PARENT);
FrameLayout framelayout = new FrameLayout(this);
framelayout.setLayoutParams(framelayout_params);
// Cocos2dxEditText layout
ViewGroup.LayoutParams edittext_layout_params = new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.FILL_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
Cocos2dxEditText edittext = new Cocos2dxEditText(this);
edittext.setLayoutParams(edittext_layout_params);
// ...add to FrameLayout
framelayout.addView(edittext);
// Cocos2dxGLSurfaceView
this.mGLSurfaceView = this.onCreateView();
// ...add to FrameLayout
framelayout.addView(this.mGLSurfaceView);
// Switch to supported OpenGL (ARGB888) mode on emulator
if (isAndroidEmulator())
this.mGLSurfaceView.setEGLConfigChooser(8, 8, 8, 8, 16, 0);
this.mGLSurfaceView.setCocos2dxRenderer(new Cocos2dxRenderer());
this.mGLSurfaceView.setCocos2dxEditText(edittext);
// Set framelayout as the content view
setContentView(framelayout);
}
public Cocos2dxGLSurfaceView onCreateView() {
return new Cocos2dxGLSurfaceView(this);
}
private final static boolean isAndroidEmulator() {
String model = Build.MODEL;
Log.d(TAG, "model=" + model);
String product = Build.PRODUCT;
Log.d(TAG, "product=" + product);
boolean isEmulator = false;
if (product != null) {
isEmulator = product.equals("sdk") || product.contains("_sdk")
|| product.contains("sdk_");
}
Log.d(TAG, "isEmulator=" + isEmulator);
return isEmulator;
}
// ===========================================================
// Inner and Anonymous Classes
// ===========================================================
}
/****************************************************************************
Copyright (c) 2010-2011 cocos2d-x.org
http://www.cocos2d-x.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
package org.cocos2dx.lib;
import android.content.Context;
import android.opengl.GLSurfaceView;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.util.Log;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.inputmethod.InputMethodManager;
public class Cocos2dxGLSurfaceView extends GLSurfaceView {
// ===========================================================
// Constants
// ===========================================================
private static final String TAG = Cocos2dxGLSurfaceView.class.getSimpleName();
private final static int HANDLER_OPEN_IME_KEYBOARD = 2;
private final static int HANDLER_CLOSE_IME_KEYBOARD = 3;
// ===========================================================
// Fields
// ===========================================================
// TODO Static handler -> Potential leak!
private static Handler sHandler;
private static Cocos2dxGLSurfaceView mCocos2dxGLSurfaceView;
private static Cocos2dxTextInputWraper sCocos2dxTextInputWraper;
public Cocos2dxRenderer mCocos2dxRenderer;
private Cocos2dxEditText mCocos2dxEditText;
// ===========================================================
// Constructors
// ===========================================================
public Cocos2dxGLSurfaceView(final Context context) {
super(context);
this.initView();
}
public Cocos2dxGLSurfaceView(final Context context, final AttributeSet attrs) {
super(context, attrs);
this.initView();
}
protected void initView() {
this.setEGLContextClientVersion(2);
this.setFocusableInTouchMode(true);
Cocos2dxGLSurfaceView.mCocos2dxGLSurfaceView = this;
Cocos2dxGLSurfaceView.sCocos2dxTextInputWraper = new Cocos2dxTextInputWraper(this);
Cocos2dxGLSurfaceView.sHandler = new Handler() {
@Override
public void handleMessage(final Message msg) {
switch (msg.what) {
case HANDLER_OPEN_IME_KEYBOARD:
if (null != Cocos2dxGLSurfaceView.this.mCocos2dxEditText && Cocos2dxGLSurfaceView.this.mCocos2dxEditText.requestFocus()) {
Cocos2dxGLSurfaceView.this.mCocos2dxEditText.removeTextChangedListener(Cocos2dxGLSurfaceView.sCocos2dxTextInputWraper);
Cocos2dxGLSurfaceView.this.mCocos2dxEditText.setText("");
final String text = (String) msg.obj;
Cocos2dxGLSurfaceView.this.mCocos2dxEditText.append(text);
Cocos2dxGLSurfaceView.sCocos2dxTextInputWraper.setOriginText(text);
Cocos2dxGLSurfaceView.this.mCocos2dxEditText.addTextChangedListener(Cocos2dxGLSurfaceView.sCocos2dxTextInputWraper);
final InputMethodManager imm = (InputMethodManager) Cocos2dxGLSurfaceView.mCocos2dxGLSurfaceView.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
imm.showSoftInput(Cocos2dxGLSurfaceView.this.mCocos2dxEditText, 0);
Log.d("GLSurfaceView", "showSoftInput");
}
break;
case HANDLER_CLOSE_IME_KEYBOARD:
if (null != Cocos2dxGLSurfaceView.this.mCocos2dxEditText) {
Cocos2dxGLSurfaceView.this.mCocos2dxEditText.removeTextChangedListener(Cocos2dxGLSurfaceView.sCocos2dxTextInputWraper);
final InputMethodManager imm = (InputMethodManager) Cocos2dxGLSurfaceView.mCocos2dxGLSurfaceView.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(Cocos2dxGLSurfaceView.this.mCocos2dxEditText.getWindowToken(), 0);
Cocos2dxGLSurfaceView.this.requestFocus();
Log.d("GLSurfaceView", "HideSoftInput");
}
break;
}
}
};
}
// ===========================================================
// Getter & Setter
// ===========================================================
public static Cocos2dxGLSurfaceView getInstance() {
return mCocos2dxGLSurfaceView;
}
public static void queueAccelerometer(final float x, final float y, final float z, final long timestamp) {
mCocos2dxGLSurfaceView.queueEvent(new Runnable() {
@Override
public void run() {
Cocos2dxAccelerometer.onSensorChanged(x, y, z, timestamp);
}
});
}
public void setCocos2dxRenderer(final Cocos2dxRenderer renderer) {
this.mCocos2dxRenderer = renderer;
this.setRenderer(this.mCocos2dxRenderer);
}
private String getContentText() {
return this.mCocos2dxRenderer.getContentText();
}
public Cocos2dxEditText getCocos2dxEditText() {
return this.mCocos2dxEditText;
}
public void setCocos2dxEditText(final Cocos2dxEditText pCocos2dxEditText) {
this.mCocos2dxEditText = pCocos2dxEditText;
if (null != this.mCocos2dxEditText && null != Cocos2dxGLSurfaceView.sCocos2dxTextInputWraper) {
this.mCocos2dxEditText.setOnEditorActionListener(Cocos2dxGLSurfaceView.sCocos2dxTextInputWraper);
this.mCocos2dxEditText.setCocos2dxGLSurfaceView(this);
this.requestFocus();
}
}
// ===========================================================
// Methods for/from SuperClass/Interfaces
// ===========================================================
@Override
public void onResume() {
super.onResume();
this.setRenderMode(RENDERMODE_CONTINUOUSLY);
this.queueEvent(new Runnable() {
@Override
public void run() {
try {
Cocos2dxGLSurfaceView.this.mCocos2dxRenderer.handleOnResume();
} catch(NullPointerException ex) {
Log.w(TAG, "caught a null pointer exception, not preventing yet");
}
}
});
}
@Override
public void onPause() {
this.queueEvent(new Runnable() {
@Override
public void run() {
try {
Cocos2dxGLSurfaceView.this.mCocos2dxRenderer.handleOnPause();
} catch(NullPointerException ex) {
Log.w(TAG, "caught a null pointer exception, not preventing yet");
}
}
});
this.setRenderMode(RENDERMODE_WHEN_DIRTY);
//super.onPause();
}
@Override
public boolean onTouchEvent(final MotionEvent pMotionEvent) {
// these data are used in ACTION_MOVE and ACTION_CANCEL
final int pointerNumber = pMotionEvent.getPointerCount();
final int[] ids = new int[pointerNumber];
final float[] xs = new float[pointerNumber];
final float[] ys = new float[pointerNumber];
for (int i = 0; i < pointerNumber; i++) {
ids[i] = pMotionEvent.getPointerId(i);
xs[i] = pMotionEvent.getX(i);
ys[i] = pMotionEvent.getY(i);
}
switch (pMotionEvent.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_POINTER_DOWN:
final int indexPointerDown = pMotionEvent.getAction() >> MotionEvent.ACTION_POINTER_ID_SHIFT;
final int idPointerDown = pMotionEvent.getPointerId(indexPointerDown);
final float xPointerDown = pMotionEvent.getX(indexPointerDown);
final float yPointerDown = pMotionEvent.getY(indexPointerDown);
this.queueEvent(new Runnable() {
@Override
public void run() {
Cocos2dxGLSurfaceView.this.mCocos2dxRenderer.handleActionDown(idPointerDown, xPointerDown, yPointerDown);
}
});
break;
case MotionEvent.ACTION_DOWN:
// there are only one finger on the screen
final int idDown = pMotionEvent.getPointerId(0);
final float xDown = xs[0];
final float yDown = ys[0];
this.queueEvent(new Runnable() {
@Override
public void run() {
Cocos2dxGLSurfaceView.this.mCocos2dxRenderer.handleActionDown(idDown, xDown, yDown);
}
});
break;
case MotionEvent.ACTION_MOVE:
this.queueEvent(new Runnable() {
@Override
public void run() {
Cocos2dxGLSurfaceView.this.mCocos2dxRenderer.handleActionMove(ids, xs, ys);
}
});
break;
case MotionEvent.ACTION_POINTER_UP:
final int indexPointUp = pMotionEvent.getAction() >> MotionEvent.ACTION_POINTER_ID_SHIFT;
final int idPointerUp = pMotionEvent.getPointerId(indexPointUp);
final float xPointerUp = pMotionEvent.getX(indexPointUp);
final float yPointerUp = pMotionEvent.getY(indexPointUp);
this.queueEvent(new Runnable() {
@Override
public void run() {
Cocos2dxGLSurfaceView.this.mCocos2dxRenderer.handleActionUp(idPointerUp, xPointerUp, yPointerUp);
}
});
break;
case MotionEvent.ACTION_UP:
// there are only one finger on the screen
final int idUp = pMotionEvent.getPointerId(0);
final float xUp = xs[0];
final float yUp = ys[0];
this.queueEvent(new Runnable() {
@Override
public void run() {
Cocos2dxGLSurfaceView.this.mCocos2dxRenderer.handleActionUp(idUp, xUp, yUp);
}
});
break;
case MotionEvent.ACTION_CANCEL:
this.queueEvent(new Runnable() {
@Override
public void run() {
Cocos2dxGLSurfaceView.this.mCocos2dxRenderer.handleActionCancel(ids, xs, ys);
}
});
break;
}
/*
if (BuildConfig.DEBUG) {
Cocos2dxGLSurfaceView.dumpMotionEvent(pMotionEvent);
}
*/
return true;
}
/*
* This function is called before Cocos2dxRenderer.nativeInit(), so the
* width and height is correct.
*/
@Override
protected void onSizeChanged(final int pNewSurfaceWidth, final int pNewSurfaceHeight, final int pOldSurfaceWidth, final int pOldSurfaceHeight) {
if(!this.isInEditMode()) {
this.mCocos2dxRenderer.setScreenWidthAndHeight(pNewSurfaceWidth, pNewSurfaceHeight);
}
}
@Override
public boolean onKeyDown(final int pKeyCode, final KeyEvent pKeyEvent) {
switch (pKeyCode) {
case KeyEvent.KEYCODE_BACK:
case KeyEvent.KEYCODE_MENU:
this.queueEvent(new Runnable() {
@Override
public void run() {
Cocos2dxGLSurfaceView.this.mCocos2dxRenderer.handleKeyDown(pKeyCode);
}
});
return true;
default:
return super.onKeyDown(pKeyCode, pKeyEvent);
}
}
// ===========================================================
// Methods
// ===========================================================
// ===========================================================
// Inner and Anonymous Classes
// ===========================================================
public static void openIMEKeyboard() {
final Message msg = new Message();
msg.what = Cocos2dxGLSurfaceView.HANDLER_OPEN_IME_KEYBOARD;
msg.obj = Cocos2dxGLSurfaceView.mCocos2dxGLSurfaceView.getContentText();
Cocos2dxGLSurfaceView.sHandler.sendMessage(msg);
}
public static void closeIMEKeyboard() {
final Message msg = new Message();
msg.what = Cocos2dxGLSurfaceView.HANDLER_CLOSE_IME_KEYBOARD;
Cocos2dxGLSurfaceView.sHandler.sendMessage(msg);
}
public void insertText(final String pText) {
this.queueEvent(new Runnable() {
@Override
public void run() {
Cocos2dxGLSurfaceView.this.mCocos2dxRenderer.handleInsertText(pText);
}
});
}
public void deleteBackward() {
this.queueEvent(new Runnable() {
@Override
public void run() {
Cocos2dxGLSurfaceView.this.mCocos2dxRenderer.handleDeleteBackward();
}
});
}
private static void dumpMotionEvent(final MotionEvent event) {
final String names[] = { "DOWN", "UP", "MOVE", "CANCEL", "OUTSIDE", "POINTER_DOWN", "POINTER_UP", "7?", "8?", "9?" };
final StringBuilder sb = new StringBuilder();
final int action = event.getAction();
final int actionCode = action & MotionEvent.ACTION_MASK;
sb.append("event ACTION_").append(names[actionCode]);
if (actionCode == MotionEvent.ACTION_POINTER_DOWN || actionCode == MotionEvent.ACTION_POINTER_UP) {
sb.append("(pid ").append(action >> MotionEvent.ACTION_POINTER_ID_SHIFT);
sb.append(")");
}
sb.append("[");
for (int i = 0; i < event.getPointerCount(); i++) {
sb.append("#").append(i);
sb.append("(pid ").append(event.getPointerId(i));
sb.append(")=").append((int) event.getX(i));
sb.append(",").append((int) event.getY(i));
if (i + 1 < event.getPointerCount()) {
sb.append(";");
}
}
sb.append("]");
Log.d(Cocos2dxGLSurfaceView.TAG, sb.toString());
}
}
/****************************************************************************
Copyright (c) 2010-2013 cocos2d-x.org
http://www.cocos2d-x.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
package org.cocos2dx.lib;
import java.io.UnsupportedEncodingException;
import java.util.Locale;
import android.app.Activity;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.pm.ApplicationInfo;
import android.content.res.AssetManager;
import android.os.Build;
import android.os.Environment;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.Display;
import android.view.WindowManager;
public class Cocos2dxHelper {
// ===========================================================
// Constants
// ===========================================================
private static final String PREFS_NAME = "Cocos2dxPrefsFile";
private static final String TAG = Cocos2dxHelper.class.getSimpleName();
// ===========================================================
// Fields
// ===========================================================
private static Cocos2dxMusic sCocos2dMusic;
private static Cocos2dxSound sCocos2dSound;
private static AssetManager sAssetManager;
private static Cocos2dxAccelerometer sCocos2dxAccelerometer;
private static boolean sAccelerometerEnabled;
private static String sPackageName;
private static String sNativeApkPath;
private static String sFileDirectory;
private static Context sContext = null;
private static Cocos2dxHelperListener sCocos2dxHelperListener;
// ===========================================================
// Expansion Files for Google Play
// ===========================================================
// Replace:
// com.package.name -> your reverse domain package name
// 9999 -> your manifest buildVersion
private static final String expansionPath = "/Android/obb/com.package.name/";
private static final String expansionFileName = "main.9999.com.package.name.obb";
private static boolean usingExpansionFiles;
private static int expansionMainVersion;
private static int expansionPatchVersion;
// ===========================================================
// Constructors
// ===========================================================
public static void init(final Context pContext, final Cocos2dxHelperListener pCocos2dxHelperListener) {
Cocos2dxHelper.init(pContext, pCocos2dxHelperListener, false, 0, 0);
}
public static void init(final Context pContext, final Cocos2dxHelperListener pCocos2dxHelperListener,
boolean useExpansionFiles, int main, int patch) {
final ApplicationInfo applicationInfo = pContext.getApplicationInfo();
Log.i(TAG, "INIT: main = " + main + ", patch = " + patch);
Cocos2dxHelper.sContext = pContext;
Cocos2dxHelper.sCocos2dxHelperListener = pCocos2dxHelperListener;
Cocos2dxHelper.usingExpansionFiles = useExpansionFiles;
Cocos2dxHelper.expansionMainVersion = main;
Cocos2dxHelper.expansionPatchVersion = patch;
Cocos2dxHelper.sPackageName = applicationInfo.packageName;
Cocos2dxHelper.sFileDirectory = pContext.getFilesDir().getAbsolutePath();
if(Cocos2dxHelper.usingExpansionFiles)
{
//THIS LINE IS ESSENTIAL IN ORDER TO READ FROM THE OBB FILE
Log.i(TAG, "dir: " + Environment.getExternalStorageDirectory());
Cocos2dxHelper.sNativeApkPath = Environment.getExternalStorageDirectory() + Cocos2dxHelper.expansionPath + expansionFileName;
Log.i(TAG, "apk: " + Cocos2dxHelper.sNativeApkPath);
Cocos2dxHelper.nativeSetApkPath(Cocos2dxHelper.sNativeApkPath);
}
else
{
Cocos2dxHelper.nativeSetApkPath(applicationInfo.sourceDir);
}
Cocos2dxHelper.sCocos2dxAccelerometer = new Cocos2dxAccelerometer(pContext);
Cocos2dxHelper.sCocos2dMusic = new Cocos2dxMusic(pContext, Cocos2dxHelper.usingExpansionFiles, Cocos2dxHelper.expansionMainVersion, Cocos2dxHelper.expansionPatchVersion);
Cocos2dxHelper.sCocos2dSound = new Cocos2dxSound(pContext, Cocos2dxHelper.usingExpansionFiles, Cocos2dxHelper.expansionMainVersion, Cocos2dxHelper.expansionPatchVersion);
Cocos2dxHelper.sAssetManager = pContext.getAssets();
Cocos2dxBitmap.setContext(pContext);
Cocos2dxETCLoader.setContext(pContext);
}
// ===========================================================
// Getter & Setters
// ===========================================================
// ===========================================================
// Methods for/from SuperClass/Interfaces
// ===========================================================
// ===========================================================
// Methods
// ===========================================================
private static native void nativeSetApkPath(final String pApkPath);
private static native void nativeSetEditTextDialogResult(final byte[] pBytes);
public static String getCocos2dxsNativeApkPath() {
return Cocos2dxHelper.sNativeApkPath;
}
public static String getCocos2dxPackageName() {
return Cocos2dxHelper.sPackageName;
}
public static String getCocos2dxWritablePath() {
return Cocos2dxHelper.sFileDirectory;
}
public static String getCurrentLanguage() {
return Locale.getDefault().getLanguage();
}
public static String getDeviceModel(){
return Build.MODEL;
}
public static AssetManager getAssetManager() {
return Cocos2dxHelper.sAssetManager;
}
public static void enableAccelerometer() {
Cocos2dxHelper.sAccelerometerEnabled = true;
Cocos2dxHelper.sCocos2dxAccelerometer.enable();
}
public static void setAccelerometerInterval(float interval) {
Cocos2dxHelper.sCocos2dxAccelerometer.setInterval(interval);
}
public static void disableAccelerometer() {
Cocos2dxHelper.sAccelerometerEnabled = false;
Cocos2dxHelper.sCocos2dxAccelerometer.disable();
}
public static void preloadBackgroundMusic(final String pPath) {
Cocos2dxHelper.sCocos2dMusic.preloadBackgroundMusic(pPath);
}
public static void playBackgroundMusic(final String pPath, final boolean isLoop) {
Cocos2dxHelper.sCocos2dMusic.playBackgroundMusic(pPath, isLoop);
}
public static void resumeBackgroundMusic() {
Cocos2dxHelper.sCocos2dMusic.resumeBackgroundMusic();
}
public static void pauseBackgroundMusic() {
Cocos2dxHelper.sCocos2dMusic.pauseBackgroundMusic();
}
public static void stopBackgroundMusic() {
Cocos2dxHelper.sCocos2dMusic.stopBackgroundMusic();
}
public static void rewindBackgroundMusic() {
Cocos2dxHelper.sCocos2dMusic.rewindBackgroundMusic();
}
public static void seekBackgroundMusic(final float timestamp) {
Cocos2dxHelper.sCocos2dMusic.seekBackgroundMusic(timestamp);
}
public static float getBackgroundMusicCurrentTimestamp() {
return Cocos2dxHelper.sCocos2dMusic.getBackgroundMusicCurrentTimestamp();
}
public static boolean isBackgroundMusicPlaying() {
return Cocos2dxHelper.sCocos2dMusic.isBackgroundMusicPlaying();
}
public static float getBackgroundMusicVolume() {
return Cocos2dxHelper.sCocos2dMusic.getBackgroundVolume();
}
public static void setBackgroundMusicVolume(final float volume) {
Cocos2dxHelper.sCocos2dMusic.setBackgroundVolume(volume);
}
public static void preloadEffect(final String path) {
Cocos2dxHelper.sCocos2dSound.preloadEffect(path);
}
public static int playEffect(final String path, final boolean isLoop) {
return Cocos2dxHelper.sCocos2dSound.playEffect(path, isLoop);
}
public static void resumeEffect(final int soundId) {
Cocos2dxHelper.sCocos2dSound.resumeEffect(soundId);
}
public static void pauseEffect(final int soundId) {
Cocos2dxHelper.sCocos2dSound.pauseEffect(soundId);
}
public static void stopEffect(final int soundId) {
Cocos2dxHelper.sCocos2dSound.stopEffect(soundId);
}
public static float getEffectsVolume() {
return Cocos2dxHelper.sCocos2dSound.getEffectsVolume();
}
public static void setEffectsVolume(final float volume) {
Cocos2dxHelper.sCocos2dSound.setEffectsVolume(volume);
}
public static void unloadEffect(final String path) {
Cocos2dxHelper.sCocos2dSound.unloadEffect(path);
}
public static void pauseAllEffects() {
Cocos2dxHelper.sCocos2dSound.pauseAllEffects();
}
public static void resumeAllEffects() {
Cocos2dxHelper.sCocos2dSound.resumeAllEffects();
}
public static void stopAllEffects() {
Cocos2dxHelper.sCocos2dSound.stopAllEffects();
}
public static void end() {
Cocos2dxHelper.sCocos2dMusic.end();
Cocos2dxHelper.sCocos2dSound.end();
}
public static void onResume() {
if (Cocos2dxHelper.sAccelerometerEnabled) {
Cocos2dxHelper.sCocos2dxAccelerometer.enable();
}
}
public static void onPause() {
if (Cocos2dxHelper.sAccelerometerEnabled) {
Cocos2dxHelper.sCocos2dxAccelerometer.disable();
}
}
public static void terminateProcess() {
Log.d("stupid", "Terminating Process");
android.os.Process.killProcess(android.os.Process.myPid());
}
private static void showDialog(final String pTitle, final String pMessage) {
Cocos2dxHelper.sCocos2dxHelperListener.showDialog(pTitle, pMessage);
}
private static void showEditTextDialog(final String pTitle, final String pMessage, final int pInputMode, final int pInputFlag, final int pReturnType, final int pMaxLength) {
Cocos2dxHelper.sCocos2dxHelperListener.showEditTextDialog(pTitle, pMessage, pInputMode, pInputFlag, pReturnType, pMaxLength);
}
private static void extraLog(final String pMessage) {
Cocos2dxHelper.sCocos2dxHelperListener.extraLog(pMessage);
}
public static void setEditTextDialogResult(final String pResult) {
try {
final byte[] bytesUTF8 = pResult.getBytes("UTF8");
Cocos2dxHelper.sCocos2dxHelperListener.runOnGLThread(new Runnable() {
@Override
public void run() {
Cocos2dxHelper.nativeSetEditTextDialogResult(bytesUTF8);
}
});
} catch (UnsupportedEncodingException pUnsupportedEncodingException) {
/* Nothing. */
}
}
public static int getDPI()
{
if (sContext != null)
{
DisplayMetrics metrics = new DisplayMetrics();
WindowManager wm = ((Activity)sContext).getWindowManager();
if (wm != null)
{
Display d = wm.getDefaultDisplay();
if (d != null)
{
d.getMetrics(metrics);
return (int)(metrics.density*160.0f);
}
}
}
return -1;
}
// ===========================================================
// Functions for CCUserDefault
// ===========================================================
public static boolean getBoolForKey(String key, boolean defaultValue) {
boolean ret = false;
try {
SharedPreferences settings = ((Activity)sContext).getSharedPreferences(Cocos2dxHelper.PREFS_NAME, 0);
ret = settings.getBoolean(key, defaultValue);
} catch (ClassCastException ex) {
Log.e(TAG, ex.getMessage());
}
return ret;
}
public static int getIntegerForKey(String key, int defaultValue) {
int ret = 0;
try {
SharedPreferences settings = ((Activity)sContext).getSharedPreferences(Cocos2dxHelper.PREFS_NAME, 0);
ret = settings.getInt(key, defaultValue);
} catch (ClassCastException ex) {
Log.e(TAG, ex.getMessage());
}
return ret;
}
public static float getFloatForKey(String key, float defaultValue) {
float ret = 0.0f;
try {
SharedPreferences settings = ((Activity)sContext).getSharedPreferences(Cocos2dxHelper.PREFS_NAME, 0);
ret = settings.getFloat(key, defaultValue);
} catch (ClassCastException ex) {
Log.e(TAG, ex.getMessage());
}
return ret;
}
public static double getDoubleForKey(String key, double defaultValue) {
// SharedPreferences doesn't support saving double value
double ret = 0.0;
try {
SharedPreferences settings = ((Activity)sContext).getSharedPreferences(Cocos2dxHelper.PREFS_NAME, 0);
ret = settings.getFloat(key, (float) defaultValue);
} catch (ClassCastException ex) {
Log.e(TAG, ex.getMessage());
}
return ret;
}
public static String getStringForKey(String key, String defaultValue) {
String ret = "";
try {
SharedPreferences settings = ((Activity)sContext).getSharedPreferences(Cocos2dxHelper.PREFS_NAME, 0);
ret = settings.getString(key, defaultValue);
} catch (ClassCastException ex) {
Log.e(TAG, ex.getMessage());
}
return ret;
}
public static void setBoolForKey(String key, boolean value) {
SharedPreferences settings = ((Activity)sContext).getSharedPreferences(Cocos2dxHelper.PREFS_NAME, 0);
SharedPreferences.Editor editor = settings.edit();
editor.putBoolean(key, value);
editor.commit();
}
public static void setIntegerForKey(String key, int value) {
SharedPreferences settings = ((Activity)sContext).getSharedPreferences(Cocos2dxHelper.PREFS_NAME, 0);
SharedPreferences.Editor editor = settings.edit();
editor.putInt(key, value);
editor.commit();
}
public static void setFloatForKey(String key, float value) {
SharedPreferences settings = ((Activity)sContext).getSharedPreferences(Cocos2dxHelper.PREFS_NAME, 0);
SharedPreferences.Editor editor = settings.edit();
editor.putFloat(key, value);
editor.commit();
}
public static void setDoubleForKey(String key, double value) {
// SharedPreferences doesn't support recording double value
SharedPreferences settings = ((Activity)sContext).getSharedPreferences(Cocos2dxHelper.PREFS_NAME, 0);
SharedPreferences.Editor editor = settings.edit();
editor.putFloat(key, (float)value);
editor.commit();
}
public static void setStringForKey(String key, String value) {
SharedPreferences settings = ((Activity)sContext).getSharedPreferences(Cocos2dxHelper.PREFS_NAME, 0);
SharedPreferences.Editor editor = settings.edit();
editor.putString(key, value);
editor.commit();
}
// ===========================================================
// Inner and Anonymous Classes
// ===========================================================
public static interface Cocos2dxHelperListener {
public void showDialog(final String pTitle, final String pMessage);
public void showEditTextDialog(final String pTitle, final String pMessage, final int pInputMode, final int pInputFlag, final int pReturnType, final int pMaxLength);
public void extraLog(final String pMessage);
public void runOnGLThread(final Runnable pRunnable);
}
}
/****************************************************************************
Copyright (c) 2010-2011 cocos2d-x.org
http://www.cocos2d-x.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
package org.cocos2dx.lib;
import java.io.FileInputStream;
import java.io.IOException;
import com.android.vending.expansion.zipfile.APKExpansionSupport;
import com.android.vending.expansion.zipfile.ZipResourceFile;
import android.content.Context;
import android.content.res.AssetFileDescriptor;
import android.media.MediaPlayer;
import android.util.Log;
public class Cocos2dxMusic {
// ===========================================================
// Constants
// ===========================================================
private static final String TAG = Cocos2dxMusic.class.getSimpleName();
// ===========================================================
// Fields
// ===========================================================
private final Context mContext;
private MediaPlayer mBackgroundMediaPlayer;
private float mLeftVolume;
private float mRightVolume;
private boolean mPaused;
private String mCurrentPath;
private boolean usingExpansionFiles;
private int expansionMainVersion;
private int expansionPatchVersion;
private static ZipResourceFile zip_resource_file = null;
// ===========================================================
// Constructors
// ===========================================================
public Cocos2dxMusic(final Context pContext) {
this(pContext, false, 0, 0);
}
public Cocos2dxMusic(final Context pContext, boolean useExpansionFiles, int main, int patch) {
this.mContext = pContext;
this.usingExpansionFiles = useExpansionFiles;
this.expansionMainVersion = main;
this.expansionPatchVersion = patch;
if(this.usingExpansionFiles)
{
Log.i(TAG, "using expansion files");
// EXPANSION: expansion file support
try {
// Change the second argument to match with your version code
zip_resource_file = APKExpansionSupport.getAPKExpansionZipFile(pContext,
this.expansionMainVersion, this.expansionPatchVersion);
} catch (IOException e) {
Log.e("Cocos2dxMusic", "Error initialising ZipResourceFile ", e);
}
}
this.initData();
}
// ===========================================================
// Getter & Setter
// ===========================================================
// ===========================================================
// Methods for/from SuperClass/Interfaces
// ===========================================================
// ===========================================================
// Methods
// ===========================================================
public void preloadBackgroundMusic(final String pPath) {
if ((this.mCurrentPath == null) || (!this.mCurrentPath.equals(pPath))) {
// preload new background music
// release old resource and create a new one
if (this.mBackgroundMediaPlayer != null) {
this.mBackgroundMediaPlayer.release();
}
this.mBackgroundMediaPlayer = this.createMediaplayer(pPath);
// record the path
this.mCurrentPath = pPath;
}
}
public void playBackgroundMusic(final String pPath, final boolean isLoop) {
if (this.mCurrentPath == null) {
// it is the first time to play background music or end() was called
this.mBackgroundMediaPlayer = this.createMediaplayer(pPath);
this.mCurrentPath = pPath;
} else {
if (!this.mCurrentPath.equals(pPath)) {
// play new background music
// release old resource and create a new one
if (this.mBackgroundMediaPlayer != null) {
this.mBackgroundMediaPlayer.release();
}
this.mBackgroundMediaPlayer = this.createMediaplayer(pPath);
// record the path
this.mCurrentPath = pPath;
}
}
if (this.mBackgroundMediaPlayer == null) {
Log.e(Cocos2dxMusic.TAG, "playBackgroundMusic: background media player is null");
} else {
// if the music is playing or paused, stop it
this.mBackgroundMediaPlayer.stop();
this.mBackgroundMediaPlayer.setLooping(isLoop);
try {
this.mBackgroundMediaPlayer.prepare();
this.mBackgroundMediaPlayer.seekTo(0);
this.mBackgroundMediaPlayer.start();
this.mPaused = false;
} catch (final Exception e) {
Log.e(Cocos2dxMusic.TAG, "playBackgroundMusic: error state");
}
}
}
public void stopBackgroundMusic() {
if (this.mBackgroundMediaPlayer != null) {
this.mBackgroundMediaPlayer.stop();
// should set the state, if not, the following sequence will be
// error
// play -> pause -> stop -> resume
this.mPaused = false;
}
}
public void pauseBackgroundMusic() {
if (this.mBackgroundMediaPlayer != null && this.mBackgroundMediaPlayer.isPlaying()) {
this.mBackgroundMediaPlayer.pause();
this.mPaused = true;
}
}
public void resumeBackgroundMusic() {
if (this.mBackgroundMediaPlayer != null && this.mPaused) {
this.mBackgroundMediaPlayer.start();
this.mPaused = false;
}
}
public void rewindBackgroundMusic() {
if (this.mBackgroundMediaPlayer != null) {
this.mBackgroundMediaPlayer.stop();
try {
this.mBackgroundMediaPlayer.prepare();
this.mBackgroundMediaPlayer.seekTo(0);
this.mBackgroundMediaPlayer.start();
this.mPaused = false;
} catch (final Exception e) {
Log.e(Cocos2dxMusic.TAG, "rewindBackgroundMusic: error state");
}
}
}
public void seekBackgroundMusic(float timestamp) {
if (this.mBackgroundMediaPlayer != null) {
try {
int seekms = (int) (1000 * timestamp);
this.mBackgroundMediaPlayer.seekTo(seekms);
} catch (final Exception e) {
Log.e(Cocos2dxMusic.TAG, "seekBackgroundMusic: error state");
}
}
}
public float getBackgroundMusicCurrentTimestamp() {
float ret = 0.0f;
if (this.mBackgroundMediaPlayer != null) {
try {
int ms = this.mBackgroundMediaPlayer.getCurrentPosition();
//Log.d("MEH", "ms = " + ms);
ret = (float) ((float) ms / 1000.00);
} catch (final Exception e) {
Log.e(Cocos2dxMusic.TAG, "getBackgroundMusicCurrentTimestamp: error state");
}
}
return ret;
}
public boolean isBackgroundMusicPlaying() {
boolean ret = false;
if (this.mBackgroundMediaPlayer == null) {
ret = false;
} else {
ret = this.mBackgroundMediaPlayer.isPlaying();
}
return ret;
}
public void end() {
if (this.mBackgroundMediaPlayer != null) {
this.mBackgroundMediaPlayer.release();
}
this.initData();
}
public float getBackgroundVolume() {
if (this.mBackgroundMediaPlayer != null) {
return (this.mLeftVolume + this.mRightVolume) / 2;
} else {
return 0.0f;
}
}
public void setBackgroundVolume(float pVolume) {
if (pVolume < 0.0f) {
pVolume = 0.0f;
}
if (pVolume > 1.0f) {
pVolume = 1.0f;
}
this.mLeftVolume = this.mRightVolume = pVolume;
if (this.mBackgroundMediaPlayer != null) {
this.mBackgroundMediaPlayer.setVolume(this.mLeftVolume, this.mRightVolume);
}
}
private void initData() {
this.mLeftVolume = 0.5f;
this.mRightVolume = 0.5f;
this.mBackgroundMediaPlayer = null;
this.mPaused = false;
this.mCurrentPath = null;
}
/**
* create mediaplayer for music
*
* @param pPath
* the pPath relative to assets
* @return
*/
private MediaPlayer createMediaplayer(final String pPath) {
MediaPlayer mediaPlayer = new MediaPlayer();
try
{
if (pPath.startsWith("/"))
{
if(this.usingExpansionFiles)
{
// EXPANSION: expansion file support
final AssetFileDescriptor assetFileDescriptor = zip_resource_file.getAssetFileDescriptor("assets/" + pPath);
final FileInputStream fis = assetFileDescriptor.createInputStream();
mediaPlayer.setDataSource(fis.getFD());
fis.close();
}
else
{
final FileInputStream fis = new FileInputStream(pPath);
mediaPlayer.setDataSource(fis.getFD());
fis.close();
}
}
else
{
if(this.usingExpansionFiles)
{
// EXPANSION: expansion file support
final AssetFileDescriptor assetFileDescriptor = zip_resource_file.getAssetFileDescriptor( "assets/" + pPath );
mediaPlayer.setDataSource(assetFileDescriptor.getFileDescriptor(), assetFileDescriptor.getStartOffset(), assetFileDescriptor.getLength());
}
else
{
final AssetFileDescriptor assetFileDescritor = this.mContext.getAssets().openFd(pPath);
mediaPlayer.setDataSource(assetFileDescritor.getFileDescriptor(), assetFileDescritor.getStartOffset(), assetFileDescritor.getLength());
}
}
mediaPlayer.prepare();
mediaPlayer.setVolume(this.mLeftVolume, this.mRightVolume);
} catch (final IOException e){
Log.e(Cocos2dxMusic.TAG, "ioerror: " + e.getMessage());
} catch (final Exception e) {
mediaPlayer = null;
Log.e(Cocos2dxMusic.TAG, "error: " + e.getMessage(), e);
}
return mediaPlayer;
}
// ===========================================================
// Inner and Anonymous Classes
// ===========================================================
}
/****************************************************************************
Copyright (c) 2010-2011 cocos2d-x.org
http://www.cocos2d-x.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
package org.cocos2dx.lib;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
import android.opengl.GLSurfaceView;
public class Cocos2dxRenderer implements GLSurfaceView.Renderer {
// ===========================================================
// Constants
// ===========================================================
private final static long NANOSECONDSPERSECOND = 1000000000L;
private final static long NANOSECONDSPERMICROSECOND = 1000000;
private static long sAnimationInterval = (long) (1.0 / 60 * Cocos2dxRenderer.NANOSECONDSPERSECOND);
// ===========================================================
// Fields
// ===========================================================
private long mLastTickInNanoSeconds;
private int mScreenWidth;
private int mScreenHeight;
// ===========================================================
// Constructors
// ===========================================================
// ===========================================================
// Getter & Setter
// ===========================================================
public static void setAnimationInterval(final double pAnimationInterval) {
Cocos2dxRenderer.sAnimationInterval = (long) (pAnimationInterval * Cocos2dxRenderer.NANOSECONDSPERSECOND);
}
public void setScreenWidthAndHeight(final int pSurfaceWidth, final int pSurfaceHeight) {
this.mScreenWidth = pSurfaceWidth;
this.mScreenHeight = pSurfaceHeight;
}
// ===========================================================
// Methods for/from SuperClass/Interfaces
// ===========================================================
@Override
public void onSurfaceCreated(final GL10 pGL10, final EGLConfig pEGLConfig) {
Cocos2dxRenderer.nativeInit(this.mScreenWidth, this.mScreenHeight);
this.mLastTickInNanoSeconds = System.nanoTime();
}
@Override
public void onSurfaceChanged(final GL10 pGL10, final int pWidth, final int pHeight) {
}
@Override
public void onDrawFrame(final GL10 gl) {
/*
* FPS controlling algorithm is not accurate, and it will slow down FPS
* on some devices. So comment FPS controlling code.
*/
/*
final long nowInNanoSeconds = System.nanoTime();
final long interval = nowInNanoSeconds - this.mLastTickInNanoSeconds;
*/
// should render a frame when onDrawFrame() is called or there is a
// "ghost"
Cocos2dxRenderer.nativeRender();
/*
// fps controlling
if (interval < Cocos2dxRenderer.sAnimationInterval) {
try {
// because we render it before, so we should sleep twice time interval
Thread.sleep((Cocos2dxRenderer.sAnimationInterval - interval) / Cocos2dxRenderer.NANOSECONDSPERMICROSECOND);
} catch (final Exception e) {
}
}
this.mLastTickInNanoSeconds = nowInNanoSeconds;
*/
}
// ===========================================================
// Methods
// ===========================================================
private static native void nativeTouchesBegin(final int pID, final float pX, final float pY);
private static native void nativeTouchesEnd(final int pID, final float pX, final float pY);
private static native void nativeTouchesMove(final int[] pIDs, final float[] pXs, final float[] pYs);
private static native void nativeTouchesCancel(final int[] pIDs, final float[] pXs, final float[] pYs);
private static native boolean nativeKeyDown(final int pKeyCode);
private static native void nativeRender();
private static native void nativeInit(final int pWidth, final int pHeight);
private static native void nativeOnPause();
private static native void nativeOnResume();
public void handleActionDown(final int pID, final float pX, final float pY) {
Cocos2dxRenderer.nativeTouchesBegin(pID, pX, pY);
}
public void handleActionUp(final int pID, final float pX, final float pY) {
Cocos2dxRenderer.nativeTouchesEnd(pID, pX, pY);
}
public void handleActionCancel(final int[] pIDs, final float[] pXs, final float[] pYs) {
Cocos2dxRenderer.nativeTouchesCancel(pIDs, pXs, pYs);
}
public void handleActionMove(final int[] pIDs, final float[] pXs, final float[] pYs) {
Cocos2dxRenderer.nativeTouchesMove(pIDs, pXs, pYs);
}
public boolean handleKeyDown(final int pKeyCode) {
return Cocos2dxRenderer.nativeKeyDown(pKeyCode);
}
public void handleOnPause() {
Cocos2dxRenderer.nativeOnPause();
}
public void handleOnResume() {
Cocos2dxRenderer.nativeOnResume();
}
private static native void nativeInsertText(final String pText);
private static native void nativeDeleteBackward();
private static native String nativeGetContentText();
public void handleInsertText(final String pText) {
Cocos2dxRenderer.nativeInsertText(pText);
}
public void handleDeleteBackward() {
Cocos2dxRenderer.nativeDeleteBackward();
}
public String getContentText() {
return Cocos2dxRenderer.nativeGetContentText();
}
// ===========================================================
// Inner and Anonymous Classes
// ===========================================================
}
/****************************************************************************
Copyright (c) 2010-2011 cocos2d-x.org
http://www.cocos2d-x.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
package org.cocos2dx.lib;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.Semaphore;
import com.android.vending.expansion.zipfile.APKExpansionSupport;
import com.android.vending.expansion.zipfile.ZipResourceFile;
import android.content.Context;
import android.content.res.AssetFileDescriptor;
import android.media.AudioManager;
import android.media.SoundPool;
import android.util.Log;
public class Cocos2dxSound {
// ===========================================================
// Constants
// ===========================================================
private static final String TAG = "Cocos2dxSound";
// ===========================================================
// Fields
// ===========================================================
private final Context mContext;
private SoundPool mSoundPool;
private float mLeftVolume;
private float mRightVolume;
// sound path and stream ids map
// a file may be played many times at the same time
// so there is an array map to a file path
private final HashMap<String, ArrayList<Integer>> mPathStreamIDsMap = new HashMap<String, ArrayList<Integer>>();
private final HashMap<String, Integer> mPathSoundIDMap = new HashMap<String, Integer>();
private final ArrayList<SoundInfoForLoadedCompleted> mEffecToPlayWhenLoadedArray = new ArrayList<SoundInfoForLoadedCompleted>();
private int mStreamIdSyn;
private Semaphore mSemaphore;
private static final int MAX_SIMULTANEOUS_STREAMS_DEFAULT = 5;
private static final float SOUND_RATE = 1.0f;
private static final int SOUND_PRIORITY = 1;
private static final int SOUND_QUALITY = 5;
private final static int INVALID_SOUND_ID = -1;
private final static int INVALID_STREAM_ID = -1;
private boolean usingExpansionFiles;
private int expansionMainVersion;
private int expansionPatchVersion;
private static ZipResourceFile zip_resource_file = null;
// ===========================================================
// Constructors
// ===========================================================
public Cocos2dxSound(final Context pContext) {
this(pContext, false, 0, 0);
}
public Cocos2dxSound(final Context pContext, boolean useExpansionFiles, int main, int patch) {
this.mContext = pContext;
this.usingExpansionFiles = useExpansionFiles;
this.expansionMainVersion = main;
this.expansionPatchVersion = patch;
if(this.usingExpansionFiles)
{
// EXPANSION: expansion file support
Log.i(TAG, "using expansion files");
try {
// Change the second argument to match with your version code
zip_resource_file = APKExpansionSupport.getAPKExpansionZipFile(pContext, this.expansionMainVersion, this.expansionPatchVersion);
} catch (IOException e) {
Log.e(TAG, "Initializing ZipResourceFile: ", e);
}
}
this.initData();
}
private void initData() {
this.mSoundPool = new SoundPool(Cocos2dxSound.MAX_SIMULTANEOUS_STREAMS_DEFAULT, AudioManager.STREAM_MUSIC, Cocos2dxSound.SOUND_QUALITY);
this.mSoundPool.setOnLoadCompleteListener(new OnLoadCompletedListener());
this.mLeftVolume = 0.5f;
this.mRightVolume = 0.5f;
this.mSemaphore = new Semaphore(0, true);
}
// ===========================================================
// Getter & Setter
// ===========================================================
// ===========================================================
// Methods for/from SuperClass/Interfaces
// ===========================================================
// ===========================================================
// Methods
// ===========================================================
public int preloadEffect(final String pPath) {
Integer soundID = this.mPathSoundIDMap.get(pPath);
if (soundID == null) {
soundID = this.createSoundIDFromAsset(pPath);
// save value just in case if file is really loaded
if (soundID != Cocos2dxSound.INVALID_SOUND_ID) {
this.mPathSoundIDMap.put(pPath, soundID);
}
}
return soundID;
}
public void unloadEffect(final String pPath) {
// stop effects
final ArrayList<Integer> streamIDs = this.mPathStreamIDsMap.get(pPath);
if (streamIDs != null) {
for (final Integer pStreamID : streamIDs) {
this.mSoundPool.stop(pStreamID);
}
}
this.mPathStreamIDsMap.remove(pPath);
// unload effect
final Integer soundID = this.mPathSoundIDMap.get(pPath);
if (soundID != null) {
this.mSoundPool.unload(soundID);
this.mPathSoundIDMap.remove(pPath);
}
}
public int playEffect(final String pPath, final boolean pLoop) {
Integer soundID = this.mPathSoundIDMap.get(pPath);
int streamID = Cocos2dxSound.INVALID_STREAM_ID;
if (soundID != null) {
// play sound
streamID = this.doPlayEffect(pPath, soundID.intValue(), pLoop);
} else {
// the effect is not prepared
soundID = this.preloadEffect(pPath);
if (soundID == Cocos2dxSound.INVALID_SOUND_ID) {
// can not preload effect
return Cocos2dxSound.INVALID_SOUND_ID;
}
// only allow one playEffect at a time, or the semaphore will not
// work correctly
synchronized (this.mSoundPool) {
// add this effect into mEffecToPlayWhenLoadedArray, and it will
// be played when loaded completely
mEffecToPlayWhenLoadedArray.add(new SoundInfoForLoadedCompleted(pPath, soundID.intValue(), pLoop));
try {
// wait OnloadedCompleteListener to set streamID
this.mSemaphore.acquire();
streamID = this.mStreamIdSyn;
} catch (Exception e) {
return Cocos2dxSound.INVALID_SOUND_ID;
}
}
}
return streamID;
}
public void stopEffect(final int pStreamID) {
this.mSoundPool.stop(pStreamID);
// remove record
for (final String pPath : this.mPathStreamIDsMap.keySet()) {
if (this.mPathStreamIDsMap.get(pPath).contains(pStreamID)) {
this.mPathStreamIDsMap.get(pPath).remove(this.mPathStreamIDsMap.get(pPath).indexOf(pStreamID));
break;
}
}
}
public void pauseEffect(final int pStreamID) {
this.mSoundPool.pause(pStreamID);
}
public void resumeEffect(final int pStreamID) {
this.mSoundPool.resume(pStreamID);
}
public boolean isSoundPlaying(final int pStreamID) {
return false;
}
public void pauseAllEffects() {
this.mSoundPool.autoPause();
}
public void resumeAllEffects() {
// can not only invoke SoundPool.autoResume() here, because
// it only resumes all effects paused by pauseAllEffects()
if (!this.mPathStreamIDsMap.isEmpty()) {
final Iterator<Entry<String, ArrayList<Integer>>> iter = this.mPathStreamIDsMap.entrySet().iterator();
while (iter.hasNext()) {
final Entry<String, ArrayList<Integer>> entry = iter.next();
for (final int pStreamID : entry.getValue()) {
this.mSoundPool.resume(pStreamID);
}
}
}
}
@SuppressWarnings("unchecked")
public void stopAllEffects() {
// stop effects
if (!this.mPathStreamIDsMap.isEmpty()) {
final Iterator<?> iter = this.mPathStreamIDsMap.entrySet().iterator();
while (iter.hasNext()) {
final Map.Entry<String, ArrayList<Integer>> entry = (Map.Entry<String, ArrayList<Integer>>) iter.next();
for (final int pStreamID : entry.getValue()) {
this.mSoundPool.stop(pStreamID);
}
}
}
// remove records
this.mPathStreamIDsMap.clear();
}
public float getEffectsVolume() {
return (this.mLeftVolume + this.mRightVolume) / 2;
}
public void setEffectsVolume(float pVolume) {
// pVolume should be in [0, 1.0]
if (pVolume < 0) {
pVolume = 0;
}
if (pVolume > 1) {
pVolume = 1;
}
this.mLeftVolume = this.mRightVolume = pVolume;
// change the volume of playing sounds
if (!this.mPathStreamIDsMap.isEmpty()) {
final Iterator<Entry<String, ArrayList<Integer>>> iter = this.mPathStreamIDsMap.entrySet().iterator();
while (iter.hasNext()) {
final Entry<String, ArrayList<Integer>> entry = iter.next();
for (final int pStreamID : entry.getValue()) {
this.mSoundPool.setVolume(pStreamID, this.mLeftVolume, this.mRightVolume);
}
}
}
}
public void end() {
this.mSoundPool.release();
this.mPathStreamIDsMap.clear();
this.mPathSoundIDMap.clear();
this.mEffecToPlayWhenLoadedArray.clear();
this.mLeftVolume = 0.5f;
this.mRightVolume = 0.5f;
this.initData();
}
public int createSoundIDFromAsset(final String pPath) {
int soundID = Cocos2dxSound.INVALID_SOUND_ID;
try {
if (pPath.startsWith("/"))
{
soundID = this.mSoundPool.load(pPath, 0);
}
else
{
if(this.usingExpansionFiles)
{
// EXPANSION: expansion file support
AssetFileDescriptor assetFileDescritor = zip_resource_file.getAssetFileDescriptor("assets/" + pPath);
soundID = this.mSoundPool.load(assetFileDescritor, 0);
}
else
{
soundID = this.mSoundPool.load(this.mContext.getAssets().openFd(pPath), 0);
}
}
} catch (final Exception e) {
soundID = Cocos2dxSound.INVALID_SOUND_ID;
Log.e(Cocos2dxSound.TAG, "error: " + e.getMessage(), e);
}
// mSoundPool.load returns 0 if something goes wrong, for example a file
// does not exist
if (soundID == 0) {
soundID = Cocos2dxSound.INVALID_SOUND_ID;
}
return soundID;
}
private int doPlayEffect(final String pPath, final int soundId, final boolean pLoop) {
// play sound
int streamID = this.mSoundPool.play(soundId, this.mLeftVolume, this.mRightVolume, Cocos2dxSound.SOUND_PRIORITY, pLoop ? -1 : 0, Cocos2dxSound.SOUND_RATE);
// record stream id
ArrayList<Integer> streamIDs = this.mPathStreamIDsMap.get(pPath);
if (streamIDs == null) {
streamIDs = new ArrayList<Integer>();
this.mPathStreamIDsMap.put(pPath, streamIDs);
}
streamIDs.add(streamID);
return streamID;
}
// ===========================================================
// Inner and Anonymous Classes
// ===========================================================
public class SoundInfoForLoadedCompleted {
public int soundID;
public boolean isLoop;
public String path;
public SoundInfoForLoadedCompleted(String path, int soundId, boolean isLoop) {
this.path = path;
this.soundID = soundId;
this.isLoop = isLoop;
}
}
public class OnLoadCompletedListener implements SoundPool.OnLoadCompleteListener {
@Override
public void onLoadComplete(SoundPool soundPool, int sampleId, int status) {
if (status == 0) {
// only play effect that are in mEffecToPlayWhenLoadedArray
for (SoundInfoForLoadedCompleted info : mEffecToPlayWhenLoadedArray) {
if (sampleId == info.soundID) {
// set the stream id which will be returned by
// playEffect()
mStreamIdSyn = doPlayEffect(info.path, info.soundID, info.isLoop);
// remove it from array, because we will break here
// so it is safe to do
mEffecToPlayWhenLoadedArray.remove(info);
break;
}
}
} else {
mStreamIdSyn = Cocos2dxSound.INVALID_SOUND_ID;
}
mSemaphore.release();
}
}
}
/****************************************************************************
Copyright (c) 2010-2011 cocos2d-x.org
http://www.cocos2d-x.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
package com.package.name;
import com.package.library;
import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.zip.CRC32;
import org.cocos2dx.lib.Cocos2dxActivity;
import org.cocos2dx.lib.Cocos2dxGLSurfaceView;
import org.cocos2dx.lib.Cocos2dxHelper;
import android.app.ActivityManager;
import android.app.ActivityManager.MemoryInfo;
import android.app.AlertDialog;
import android.app.PendingIntent;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.Messenger;
import android.os.SystemClock;
import android.provider.Settings;
import android.util.Log;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.TextView;
/////////////////////////////////////////////////
// Expansion Files Imports
import com.android.vending.expansion.zipfile.ZipResourceFile;
import com.android.vending.expansion.zipfile.ZipResourceFile.ZipEntryRO;
import com.google.android.vending.expansion.downloader.Constants;
import com.google.android.vending.expansion.downloader.DownloadProgressInfo;
import com.google.android.vending.expansion.downloader.DownloaderClientMarshaller;
import com.google.android.vending.expansion.downloader.DownloaderServiceMarshaller;
import com.google.android.vending.expansion.downloader.Helpers;
import com.google.android.vending.expansion.downloader.IDownloaderClient;
import com.google.android.vending.expansion.downloader.IDownloaderService;
import com.google.android.vending.expansion.downloader.IStub;
/////////////////////////////////////////////////
/////////////////////////////////////////////////
public class GameActivity extends Cocos2dxActivity implements IDownloaderClient {
public static final int ANDROID_BUILD_GINGERBREAD = 9;
public static final int SCREEN_ORIENTATION_SENSOR_LANDSCAPE = 6;
private static final String TAG = GameActivity.class.getSimpleName();
public Cocos2dxGLSurfaceView mGLView = null;
// Expansion Files
// mainVersion -> from your manifest buildVersion (the version when uploading new/updated expansion file for first time)
// mainBytes -> determine exact bytes in some manner for confirm same (could not verify or use a hash, etc)
// note: we don't use the "patch" 2nd expansion file
private static boolean useExpansionFiles = true;
private static int expansionMainVersion = 9999;
private static long expansionMainBytes = 1112223333L;
private static int expansionPatchVersion = 0;
// ///////////////////////////////////////////////////////////////////////
// ///////////////////////////////////////////////////////////////////////
protected void initializeExpansionFiles() {
try {
/**
* Before we do anything, are the files we expect already here and
* delivered (presumably by Market) For free titles, this is
* probably worth doing. (so no Market request is necessary)
*/
if (! expansionFilesDelivered()) {
Log.i(TAG, "Attempting to download expansion file");
Intent launchIntent = GameActivity.this.getIntent();
Intent intentToLaunchThisActivityFromNotification = new Intent(GameActivity.this, GameActivity.this.getClass());
intentToLaunchThisActivityFromNotification.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
intentToLaunchThisActivityFromNotification.setAction(launchIntent.getAction());
if (launchIntent.getCategories() != null) {
for (String category : launchIntent.getCategories()) {
intentToLaunchThisActivityFromNotification.addCategory(category);
}
}
// Build PendingIntent used to open this activity from Notification
PendingIntent pendingIntent = PendingIntent.getActivity(GameActivity.this, 0, intentToLaunchThisActivityFromNotification, PendingIntent.FLAG_UPDATE_CURRENT);
// Request to start the download
int startResult = DownloaderClientMarshaller.startDownloadServiceIfRequired(this, pendingIntent, GameDownloadService.class);
if (startResult != DownloaderClientMarshaller.NO_DOWNLOAD_REQUIRED) {
// The DownloaderService has started downloading the file show progress
initializeDownloadUI();
return;
} else {
// otherwise, download not needed so we fall through to
Log.w(TAG, "NO_DOWNLOAD_REQUIRED");
}
}
// expansion files exist
// initializeDownloadUI();
// validateXAPKZipFiles();
// instead of showing validate UI, let's just start the game up
GameActivity.this.callDelayedInit();
} catch (NameNotFoundException e) {
Log.e(TAG, "Failure trying to download or validate expansion file! MAYDAY!");
e.printStackTrace();
}
}
protected void onCreate(Bundle savedInstanceState) {
Log.i(TAG, "" + Environment.getExternalStorageDirectory());
super.onCreate(savedInstanceState);
if (Build.VERSION.SDK_INT >= ANDROID_BUILD_GINGERBREAD) {
setRequestedOrientation(SCREEN_ORIENTATION_SENSOR_LANDSCAPE);
}
if(GameActivity.useExpansionFiles)
{
// GOOGLE EXPANSION FILE
Log.i(TAG, "initializing expansion files");
this.initializeExpansionFiles();
Log.d(TAG, "done.");
}
else
{
this.callDelayedInit();
}
this.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
this.savedGamesData = this.savedGamesAsString();
// Log.d(TAG, this.savedGamesData);
}
public Cocos2dxGLSurfaceView onCreateView() {
mGLView = new Cocos2dxGLSurfaceView(this);
mGLView.setEGLConfigChooser(5, 6, 5, 0, 16, 8);
return mGLView;
}
// ///////////////////////////////////////////////////////////////////////
// ///////////////////////////////////////////////////////////////////////
// ///////////////////////////////////////////////////////////////////////
@Override
protected void onResume() {
Log.i(TAG, "onResume");
if (null != mDownloaderClientStub) {
Log.d(TAG, "connecting");
mDownloaderClientStub.connect(this);
}
super.onResume();
}
/**
* Connect the stub to our service on start.
*/
@Override
protected void onStart() {
Log.d(TAG, "onStart");
// if (null != mDownloaderClientStub) {
// Log.d(TAG, "connecting");
// mDownloaderClientStub.connect(this);
// }
super.onStart();
}
/**
* Disconnect the stub from our service on stop
*/
@Override
protected void onStop() {
this.mCancelValidation = true;
if (null != mDownloaderClientStub) {
Log.i(TAG, "disconnecting");
mDownloaderClientStub.disconnect(this);
}
try {
FlurryAgent.onEndSession(this);
} catch(Exception ex) {
Log.e(TAG,"exception in flurry onEndSession");
}
super.onStop();
}
@Override
protected void onDestroy() {
this.mCancelValidation = true;
if (null != mDownloaderClientStub) {
Log.i(TAG, "disconnecting");
mDownloaderClientStub.disconnect(this);
}
try {
FlurryAgent.onEndSession(this);
Log.i(TAG, "onDestroy");
} catch(Exception ex) {
Log.e(TAG,"exception in flurry onEndSession");
}
super.onDestroy();
}
// ///////////////////////////////////////////////////////////////////////
// ///////////////////////////////////////////////////////////////////////
// ///////////////////////////////////////////////////////////////////////
public void callDelayedInit() {
this.delayedInit(useExpansionFiles, expansionMainVersion, expansionPatchVersion);
try {
ActivityManager activityManager = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
MemoryInfo memoryInfo = new ActivityManager.MemoryInfo();
activityManager.getMemoryInfo(memoryInfo);
int availMegs = (int) (memoryInfo.availMem / 1048576L);
int lowThreshMegs = (int) (memoryInfo.threshold / 1048576L);
int tierMegs = availMegs < 256 ? 128 : availMegs <= 512 ? 256 : availMegs <= 1024 ? 512 : availMegs <= 2048 ? 1024 : 2048;
Map<String, String> paramsMem = new HashMap<String, String>();
paramsMem.put("Memory_Avail", "" + availMegs + "MB");
paramsMem.put("Memory_Tier", "" + tierMegs + "MB");
paramsMem.put("Memory_Threshold", "" + lowThreshMegs + "MB");
paramsMem.put("Memory_Low", (memoryInfo.lowMemory ? "YES" : "NO"));
Log.i(TAG, "Memory_Avail: " + availMegs);
Log.i(TAG, "Memory_Tier: " + tierMegs);
Log.i(TAG, "Memory_Threshold: " + lowThreshMegs);
Log.i(TAG, "Memory_Low: " + (memoryInfo.lowMemory ? "YES" : "NO"));
if(android.os.Build.VERSION.SDK_INT >= 16) {
// Do something fancy
int totalMegs = (int) (memoryInfo.totalMem / 1048576L);
int totalTierMegs = totalMegs < 256 ? 128 : totalMegs <= 512 ? 256 : totalMegs <= 1024 ? 512 : totalMegs <= 2048 ? 1024 : 2048;
paramsMem.put("Memory_Total", "" + totalMegs + "MB");
paramsMem.put("Memory_Total_Tier", "" + totalTierMegs + "MB");
Log.i(TAG, "Memory_Total: " + totalMegs);
Log.i(TAG, "Memory_Total_Tier: " + totalTierMegs);
}
// store memory or high-mem into Cocos2dxHelper
Cocos2dxHelper.setIntegerForKey("AvailableDeviceMemory", availMegs);
} catch(Exception ex) {
Log.e(TAG, "error with logging memory info");
}
try {
Cocos2dxHelper.setStringForKey("AnalyticsStoreName", ANALYTICS_STORE_NAME);
} catch(Exception ex) {
Log.e(TAG, "error setStringForKey [store name]");
}
}
// ///////////////////////////////////////////////////////////////////////
// ///////////////////////////////////////////////////////////////////////
// ///////////////////////////////////////////////////////////////////////
@Override
public boolean onKeyDown(final int keyCode, KeyEvent event) {
boolean handled = false;
final int KEYCODE_BACK = 4;
final int MENU_KEYCODE = 82;
// // check if back key, do exit
// // boolean keyIsBackButton = keyCode == OuyaController.BUTTON_A ||keyCode == KEYCODE_BACK;
final boolean keyIsBackButton = (keyCode == KEYCODE_BACK);
if (keyIsBackButton)
{
handled = true;
}
if (keyCode == MENU_KEYCODE)
{
handled = true;
Log.d(TAG, "menu button pressed");
} else {
Log.d(TAG, "keycode = " + keyCode);
Log.d(TAG, "device id = " + event.getDeviceId());
Log.d(TAG, "try to send key " + keyCode + " to native");
// handled = OuyaController.onKeyDown(keyCode, event);
if (this.mGLView != null) {
this.mGLView.queueEvent(new Runnable() {
@Override
public void run() {
Log.d(TAG, "sending key " + keyCode + " down to native");
boolean nativeHandled = GameActivity.this.mGLView.mCocos2dxRenderer.handleKeyDown(keyCode);
if (!nativeHandled && keyIsBackButton) {
// TODO: if desired after unhandled in native code
}
}
});
}
}
return handled || super.onKeyDown(keyCode, event);
}
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
boolean handled = false;
// handled = OuyaController.onKeyUp(keyCode, event);
return handled || super.onKeyUp(keyCode, event);
}
@Override
public void extraLog(String msg) {
}
// /////////////////////////////////////////////////////////////////////
// ///////////////////////////////////////////////////////////////////////
// ///////////////////////////////////////////////////////////////////////
// ///////////////////////////////////////////////////////////////////////
// ///////////////////////////////////////////////////////////////////////
// ///////////////////////////////////////////////////////////////////////
private ProgressBar mPB;
private TextView mStatusText;
private TextView mProgressFraction;
private TextView mProgressPercent;
private TextView mAverageSpeed;
private TextView mTimeRemaining;
private View mDashboard;
private View mDashboard2;
private View mCellMessage;
private Button mPauseButton;
private Button mWiFiSettingsButton;
private boolean mStatePaused;
private int mState;
private IDownloaderService mRemoteService;
private IStub mDownloaderClientStub;
private void setState(int newState) {
if (mState != newState) {
mState = newState;
Log.d(TAG, "newstate = " + mState);
}
}
private void setButtonPausedState(boolean paused) {
mStatePaused = paused;
Log.d(TAG, paused ? "Paused" : "Downloading");
}
/**
* This is a little helper class that demonstrates simple testing of an
* Expansion APK file delivered by Market. You may not wish to hard-code
* things such as file lengths into your executable... and you may wish to
* turn this code off during application development.
*/
private static class XAPKFile {
public final boolean mIsMain;
public final int mFileVersion;
public final long mFileSize;
XAPKFile(boolean isMain, int fileVersion, long fileSize) {
mIsMain = isMain;
mFileVersion = fileVersion;
mFileSize = fileSize;
}
}
/**
* Here is where you place the data that the validator will use to determine
* if the file was delivered correctly. This is encoded in the source code
* so the application can easily determine whether the file has been
* properly delivered without having to talk to the server. If the
* application is using LVL for licensing, it may make sense to eliminate
* these checks and to just rely on the server.
*/
// to get file size use get info
private static final XAPKFile[] xAPKS = {
// new XAPKFile(
// true,
// 11010,
// 110049195L)
new XAPKFile(
true,
GameActivity.expansionMainVersion,
GameActivity.expansionMainBytes)
};
/**
* Go through each of the APK Expansion files defined in the structure above
* and determine if the files are present and match the required size. Free
* applications should definitely consider doing this, as this allows the
* application to be launched for the first time without having a network
* connection present. Paid applications that use LVL should probably do at
* least one LVL check that requires the network to be present, so this is
* not as necessary.
*
* @return true if they are present.
*/
boolean expansionFilesDelivered() {
for (XAPKFile xf : xAPKS) {
String fileName = Helpers.getExpansionAPKFileName(this, xf.mIsMain, xf.mFileVersion);
if (!Helpers.doesFileExist(this, fileName, xf.mFileSize, false)) {
Log.w(TAG, "file doesn't exist for " + fileName + " w/size: " + xf.mFileSize);
return false;
}
}
return true;
}
/**
* Calculating a moving average for the validation speed so we don't get
* jumpy calculations for time etc.
*/
static private final float SMOOTHING_FACTOR = 0.005f;
/**
* Used by the async task
*/
private boolean mCancelValidation;
/**
* Go through each of the Expansion APK files and open each as a zip file.
* Calculate the CRC for each file and return false if any fail to match.
*
* @return true if XAPKZipFile is successful
*/
void validateXAPKZipFiles() {
AsyncTask<Object, DownloadProgressInfo, Boolean> validationTask = new AsyncTask<Object, DownloadProgressInfo, Boolean>() {
@Override
protected Boolean doInBackground(Object... params) {
for (XAPKFile xf : xAPKS) {
String fileName = Helpers.getExpansionAPKFileName(GameActivity.this, xf.mIsMain, xf.mFileVersion);
if (!Helpers.doesFileExist(GameActivity.this, fileName, xf.mFileSize, false))
return false;
fileName = Helpers.generateSaveFileName(GameActivity.this, fileName);
ZipResourceFile zrf;
byte[] buf = new byte[1024 * 256];
try {
zrf = new ZipResourceFile(fileName);
ZipEntryRO[] entries = zrf.getAllEntries();
/**
* First calculate the total compressed length
*/
long totalCompressedLength = 0;
for (ZipEntryRO entry : entries) {
totalCompressedLength += entry.mCompressedLength;
}
float averageVerifySpeed = 0;
long totalBytesRemaining = totalCompressedLength;
long timeRemaining;
/**
* Then calculate a CRC for every file in the Zip file,
* comparing it to what is stored in the Zip directory.
* Note that for compressed Zip files we must extract
* the contents to do this comparison.
*/
for (ZipEntryRO entry : entries) {
if (-1 != entry.mCRC32) {
long length = entry.mUncompressedLength;
CRC32 crc = new CRC32();
DataInputStream dis = null;
try {
dis = new DataInputStream(zrf.getInputStream(entry.mFileName));
long startTime = SystemClock.uptimeMillis();
while (length > 0) {
int seek = (int) (length > buf.length ? buf.length : length);
dis.readFully(buf, 0, seek);
crc.update(buf, 0, seek);
length -= seek;
long currentTime = SystemClock.uptimeMillis();
long timePassed = currentTime - startTime;
if (timePassed > 0) {
float currentSpeedSample = (float) seek / (float) timePassed;
if (0 != averageVerifySpeed) {
averageVerifySpeed = SMOOTHING_FACTOR * currentSpeedSample + (1 - SMOOTHING_FACTOR) * averageVerifySpeed;
} else {
averageVerifySpeed = currentSpeedSample;
}
totalBytesRemaining -= seek;
timeRemaining = (long) (totalBytesRemaining / averageVerifySpeed);
this.publishProgress(new DownloadProgressInfo(totalCompressedLength, totalCompressedLength - totalBytesRemaining, timeRemaining,
averageVerifySpeed));
}
startTime = currentTime;
if (mCancelValidation)
return true;
}
if (crc.getValue() != entry.mCRC32) {
Log.e(Constants.TAG, "CRC does not match for entry: " + entry.mFileName);
Log.e(Constants.TAG, "In file: " + entry.getZipFileName());
return false;
}
} finally {
if (null != dis) {
dis.close();
}
}
}
}
} catch (IOException e) {
e.printStackTrace();
return false;
}
}
return true;
}
@Override
protected void onProgressUpdate(DownloadProgressInfo... values) {
mStatusText.setText("Verifying Expansion File");
onDownloadProgress(values[0]);
super.onProgressUpdate(values);
}
@Override
protected void onPostExecute(Boolean result) {
if (result) {
mDashboard.setVisibility(View.VISIBLE);
mDashboard2.setVisibility(View.VISIBLE);
mCellMessage.setVisibility(View.GONE);
mStatusText.setText(R.string.text_validation_complete);
mPauseButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
GameActivity.this.callDelayedInit();
}
});
mPauseButton.setText(android.R.string.ok);
} else {
mDashboard.setVisibility(View.VISIBLE);
mDashboard2.setVisibility(View.VISIBLE);
mCellMessage.setVisibility(View.GONE);
mStatusText.setText(R.string.text_validation_failed);
mPauseButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
finish();
}
});
mPauseButton.setText(android.R.string.cancel);
}
super.onPostExecute(result);
}
};
validationTask.execute(new Object());
}
/**
* If the download isn't present, we initialize the download UI. This ties
* all of the controls into the remote service calls.
*/
private void initializeDownloadUI() {
if (null == mDownloaderClientStub) {
mDownloaderClientStub = DownloaderClientMarshaller.CreateStub(this, GameDownloadService.class);
}
setContentView(R.layout.main);
mPB = (ProgressBar) findViewById(R.id.progressBar);
mStatusText = (TextView) findViewById(R.id.statusText);
mProgressFraction = (TextView) findViewById(R.id.progressAsFraction);
mProgressPercent = (TextView) findViewById(R.id.progressAsPercentage);
mAverageSpeed = (TextView) findViewById(R.id.progressAverageSpeed);
mTimeRemaining = (TextView) findViewById(R.id.progressTimeRemaining);
mDashboard = findViewById(R.id.downloaderDashboard);
mDashboard2 = findViewById(R.id.downloaderDashboard2);
mCellMessage = findViewById(R.id.approveCellular);
mPauseButton = (Button) findViewById(R.id.pauseButton);
mWiFiSettingsButton = (Button) findViewById(R.id.wifiSettingsButton);
mPauseButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if(null == mRemoteService) {
Log.e(TAG,"remote service not initialized");
return;
} else {
if (mStatePaused) {
mRemoteService.requestContinueDownload();
} else {
mRemoteService.requestPauseDownload();
}
}
setButtonPausedState(!mStatePaused);
}
});
mWiFiSettingsButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startActivity(new Intent(Settings.ACTION_WIFI_SETTINGS));
}
});
Button resumeOnCell = (Button) findViewById(R.id.resumeOverCellular);
resumeOnCell.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
mRemoteService.setDownloadFlags(IDownloaderService.FLAGS_DOWNLOAD_OVER_CELLULAR);
mRemoteService.requestContinueDownload();
mCellMessage.setVisibility(View.GONE);
}
});
}
/**
* Critical implementation detail. In onServiceConnected we create the
* remote service and marshaler. This is how we pass the client information
* back to the service so the client can be properly notified of changes. We
* must do this every time we reconnect to the service.
*/
@Override
public void onServiceConnected(Messenger m) {
Log.i(TAG, "onServiceConnected");
mRemoteService = DownloaderServiceMarshaller.CreateProxy(m);
mRemoteService.onClientUpdated(mDownloaderClientStub.getMessenger());
}
/**
* The download state should trigger changes in the UI --- it may be useful
* to show the state as being indeterminate at times. This sample can be
* considered a guideline.
*/
@Override
public void onDownloadStateChanged(int newState) {
Log.i(TAG, "onDownloadStateChange, newState = " + newState);
setState(newState);
boolean showDashboard = true;
boolean showCellMessage = false;
boolean paused;
boolean indeterminate;
boolean failed = false;
switch (newState) {
case IDownloaderClient.STATE_IDLE:
// STATE_IDLE means the service is listening, so it's
// safe to start making calls via mRemoteService.
paused = false;
indeterminate = true;
break;
case IDownloaderClient.STATE_CONNECTING:
case IDownloaderClient.STATE_FETCHING_URL:
showDashboard = true;
paused = false;
indeterminate = true;
break;
case IDownloaderClient.STATE_DOWNLOADING:
paused = false;
showDashboard = true;
indeterminate = false;
mStatusText.setText(getString(R.string.downloading_expansion_file));
break;
case IDownloaderClient.STATE_FAILED_CANCELED:
case IDownloaderClient.STATE_FAILED:
case IDownloaderClient.STATE_FAILED_FETCHING_URL:
case IDownloaderClient.STATE_FAILED_UNLICENSED:
paused = true;
showDashboard = false;
indeterminate = false;
failed = true;
break;
case IDownloaderClient.STATE_PAUSED_NEED_CELLULAR_PERMISSION:
case IDownloaderClient.STATE_PAUSED_WIFI_DISABLED_NEED_CELLULAR_PERMISSION:
showDashboard = false;
paused = true;
indeterminate = false;
showCellMessage = true;
break;
case IDownloaderClient.STATE_PAUSED_BY_REQUEST:
paused = true;
indeterminate = false;
break;
case IDownloaderClient.STATE_PAUSED_ROAMING:
case IDownloaderClient.STATE_PAUSED_SDCARD_UNAVAILABLE:
paused = true;
indeterminate = false;
break;
case IDownloaderClient.STATE_COMPLETED:
showDashboard = false;
paused = false;
indeterminate = false;
validateXAPKZipFiles();
return;
default:
paused = true;
indeterminate = true;
showDashboard = true;
}
if (failed) {
mDashboard.setVisibility(View.VISIBLE);
mDashboard2.setVisibility(View.VISIBLE);
mCellMessage.setVisibility(View.GONE);
mStatusText.setText("Failed to download expansion file.");
mPauseButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
GameActivity.this.initializeExpansionFiles();
}
});
mPauseButton.setText(R.string.retry);
} else {
int newDashboardVisibility = showDashboard ? View.VISIBLE : View.GONE;
if (mDashboard2.getVisibility() != newDashboardVisibility) {
mDashboard2.setVisibility(newDashboardVisibility);
}
}
int cellMessageVisibility = showCellMessage ? View.VISIBLE : View.GONE;
if (mCellMessage.getVisibility() != cellMessageVisibility) {
mCellMessage.setVisibility(cellMessageVisibility);
}
mPB.setIndeterminate(indeterminate);
setButtonPausedState(paused);
Log.d(TAG, "paused = " + paused + ", indeterminate = " + indeterminate);
}
/**
* Sets the state of the various controls based on the progressinfo object
* sent from the downloader service.
*/
@Override
public void onDownloadProgress(DownloadProgressInfo progress) {
mAverageSpeed.setText(getString(R.string.kilobytes_per_second, Helpers.getSpeedString(progress.mCurrentSpeed)));
mTimeRemaining.setText(getString(R.string.time_remaining, Helpers.getTimeRemaining(progress.mTimeRemaining)));
progress.mOverallTotal = progress.mOverallTotal;
mPB.setMax((int) (progress.mOverallTotal >> 8));
mPB.setProgress((int) (progress.mOverallProgress >> 8));
mProgressPercent.setText(Long.toString(progress.mOverallProgress * 100 / progress.mOverallTotal) + "%");
mProgressFraction.setText(Helpers.getDownloadProgressString(progress.mOverallProgress, progress.mOverallTotal));
//Log.d(TAG, "progress = " + progress.mOverallProgress + " / total = " + progress.mOverallTotal);
//Log.d(TAG, "progress.mCurrentSpeed = " + progress.mCurrentSpeed + ", progress.mTimeRemaining = " + progress.mTimeRemaining);
}
// ///////////////////////////////////////////////////////////////////////
// ///////////////////////////////////////////////////////////////////////
}
package com.package.name;
import com.google.android.vending.expansion.downloader.DownloaderClientMarshaller;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager.NameNotFoundException;
import android.util.Log;
public class GameDownloadReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
try {
Log.d("GameDownloadReceiver", "onReceive: starting download service");
DownloaderClientMarshaller.startDownloadServiceIfRequired(context, intent, GameDownloadService.class);
} catch (NameNotFoundException e) {
e.printStackTrace();
}
}
}
package com.package.name;
import android.util.Log;
import com.google.android.vending.expansion.downloader.impl.DownloaderService;
public class GameDownloadService extends DownloaderService {
private static final String LOG_TAG = GameDownloadService.class.getName();
// stuff for LVL -- MODIFY FOR YOUR APPLICATION!
private static final String BASE64_PUBLIC_KEY_GAME = "abcdefxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
// You should also modify this salt
public static final byte[] SALT = new byte[] { 0, -1, 2, 5, -3, -2, etc };
/**
* This public key comes from your Android Market publisher account, and it
* used by the LVL to validate responses from Market on your behalf.
*/
@Override
public String getPublicKey() {
Log.i(LOG_TAG, "getPublicKey");
return BASE64_PUBLIC_KEY_GAME;
}
/**
* This is used by the preference obfuscater to make sure that your
* obfuscated preferences are different than the ones used by other
* applications.
*/
@Override
public byte[] getSALT() {
return SALT;
}
/**
* Fill this in with the class name for your alarm receiver. We do this
* because receivers must be unique across all of Android (it's a good idea
* to make sure that your receiver is in your unique package)
*/
@Override
public String getAlarmReceiverClassName() {
return GameDownloadReceiver.class.getName();
}
}
/****************************************************************************
Copyright (c) 2010-2011 cocos2d-x.org
http://www.cocos2d-x.org
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
****************************************************************************/
package com.package.library;
public class GameLib {
private static final String TAG = GameLib.class.getSimpleName();
public static final int ANDROID_BUILD_GINGERBREAD = 9;
public static final int SCREEN_ORIENTATION_SENSOR_LANDSCAPE = 6;
public static final String APP_VERSION = "1.0";
public static final String APP_BUILD_VERSION = "10001";
}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="0"
android:orientation="vertical" >
<TextView
android:id="@+id/statusText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:layout_marginLeft="5dp"
android:layout_marginTop="10dp"
android:text="@string/text_initial_status"
android:textStyle="bold" />
<LinearLayout
android:id="@+id/downloaderDashboard"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical" >
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" >
<TextView
android:id="@+id/progressAsFraction"
style="@android:style/TextAppearance.Small"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_marginLeft="5dp"
android:text="@string/_0mb_0mb" >
</TextView>
<TextView
android:id="@+id/progressAsPercentage"
style="@android:style/TextAppearance.Small"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignRight="@+id/progressBar"
android:text="@string/_0_" />
<ProgressBar
android:id="@+id/progressBar"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/progressAsFraction"
android:layout_marginBottom="10dp"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
android:layout_marginTop="10dp"/>
<TextView
android:id="@+id/progressAverageSpeed"
style="@android:style/TextAppearance.Small"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_below="@+id/progressBar"
android:layout_marginLeft="5dp" />
<TextView
android:id="@+id/progressTimeRemaining"
style="@android:style/TextAppearance.Small"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignRight="@+id/progressBar"
android:layout_below="@+id/progressBar" />
</RelativeLayout>
<LinearLayout
android:id="@+id/downloaderDashboard2"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal" >
<Button
android:id="@+id/pauseButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginBottom="10dp"
android:layout_marginLeft="10dp"
android:layout_marginRight="5dp"
android:layout_marginTop="10dp"
android:layout_weight="0"
android:minHeight="40dp"
android:minWidth="94dp"
android:text="@string/text_button_pause" />
<Button
android:id="@+id/cancelButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginBottom="10dp"
android:layout_marginLeft="5dp"
android:layout_marginRight="5dp"
android:layout_marginTop="10dp"
android:layout_weight="0"
android:minHeight="40dp"
android:minWidth="94dp"
android:text="@string/text_button_cancel"
android:visibility="gone" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
<LinearLayout
android:id="@+id/approveCellular"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:orientation="vertical"
android:visibility="gone" >
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:id="@+id/textPausedParagraph1"
android:text="@string/text_paused_cellular" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:id="@+id/textPausedParagraph2"
android:text="@string/text_paused_cellular_2" />
<LinearLayout
android:id="@+id/buttonRow"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal" >
<Button
android:id="@+id/resumeOverCellular"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="10dp"
android:text="@string/text_button_resume_cellular" />
<Button
android:id="@+id/wifiSettingsButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="10dp"
android:text="@string/text_button_wifi_settings" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">Star Command</string>
<string name="text_paused_cellular">Would you like to enable downloading over cellular connections? Depending on your data plan, this may cost you money.</string>
<string name="text_paused_cellular_2">If you choose not to enable downloading over cellular connections, the download will automatically resume when wi-fi is available.</string>
<string name="text_button_resume_cellular">Resume download</string>
<string name="text_button_wifi_settings">Wi-Fi settings</string>
<string name="text_verifying_download">Verifying Download</string>
<string name="text_validation_complete">XAPK File Validation Complete. Select OK to exit.</string>
<string name="text_validation_failed">XAPK File Validation Failed.</string>
<string name="text_button_pause">Pause Download</string>
<string name="text_button_resume">Resume Download</string>
<string name="text_button_cancel">Cancel</string>
<string name="text_button_cancel_verify">Cancel Verification</string>
<string name="kilobytes_per_second">kbps</string>
<string name="time_remaining">Time Remaining</string>
<string name="_0mb_0mb">0MB / 0MB</string>
<string name="_0_">0%</string>
<string name="text_initial_status">Attempting to Download or Verify Expansion File</string>
<string name="downloading_expansion_file">Downloading Expansion File</string>
<string name="retry">Retry</string>
</resources>
@stevetranby
Copy link
Author

I'm probably missing a few things, hopefully this will help in some way until a full tutorial with proof of example test (published in store) can be created and written.

NOTE: This was developed with Cocos2dx 2.2.x so there are no guarantees it will work at all with 3.x. I'll do the tutorial example test app when I get time with 3.x.

To make things easier we put all assets (png, wav, ogg, plist, etc) into the expansion file so that it could specify single director for looking up resources. However, I think with the 3.x asset management through file paths search order could be used instead. Also, expansion files are disabled on Amazon and any other Android platforms. Hopefully I can find time to create an empty game project and upload to google play for full testing along with a tutorial.

Also you need to import three of google's libraries.

  • APK Expansion Zip Library
  • Downloader Library
  • Licensing Library

Resources

api docs
http://developer.android.com/google/play/expansion-files.html
adding licensing
http://developer.android.com/google/play/licensing/adding-licensing.html
upload w/out exp file first
https://support.google.com/googleplay/android-developer/answer/2481797?hl=en

Files and changes description

FileUtils.cpp: changed to use zip file and obb path if using extension files.
FileUtilsAndroid.cpp: changed to use zip file and obb path if using extension files.
Cocos2dxActivity.java: enable support for using expansion files and using new inits in the helper classes
Cocos2dxGLSurfaceView.java: Nothing needed here. I think the patch file showed changes to onPause/onResume in this class.
Cocos2dxHelper.java: setting the nativeAPKPath here and flag for using exp. files or not
Cocos2dxMusic.java: stream music using native APK zip
Cocos2dxSound.java: play audio using native APK zip
GameActivity.java: The meat of the game's code changes. Interfacing with the changes to Cocos2dxActivity for delayed init is first methods. Last section of methods is where the downloading expansion files occurs. It's kinda a mess, but implements the various callback methods to receive progress updates on the expansion file download status.
GameDownloadReceiver.java: pretty much Google's version
GameDownloadService.java: pretty much Google's version, just replace BASE64_PUBLIC_KEY_GAME data as well as SALT
GameLib.java: small class to use between Google, Amazon, Samsung, and DRM-Free versions

@stevetranby
Copy link
Author

Added main.xml and strings.xml. They were found at:

./res/layout/main.xml
./res/values/strings.xml

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment