package com.openfeint.internal;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Random;
import java.lang.Runnable;
import org.apache.commons.codec.binary.Hex;
import org.apache.http.impl.client.AbstractHttpClient;
import org.codehaus.jackson.JsonFactory;
import org.codehaus.jackson.JsonParser;
import org.xmlpull.v1.XmlPullParser;
import android.app.AlertDialog;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Resources;
import android.content.res.XmlResourceParser;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.Bundle;
import android.os.Handler;
import android.util.DisplayMetrics;
import android.util.Log;
import com.openfeint.api.Notification;
import com.openfeint.api.OpenFeintDelegate;
import com.openfeint.api.OpenFeintSettings;
import com.openfeint.api.R;
import com.openfeint.api.Notification.Category;
import com.openfeint.api.Notification.Type;
import com.openfeint.api.resource.CurrentUser;
import com.openfeint.api.resource.User;
import com.openfeint.internal.db.DB;
import com.openfeint.internal.notifications.SimpleNotification;
import com.openfeint.internal.notifications.TwoLineNotification;
import com.openfeint.internal.request.BaseRequest;
import com.openfeint.internal.request.BlobPostRequest;
import com.openfeint.internal.request.Client;
import com.openfeint.internal.request.GenericRequest;
import com.openfeint.internal.request.IRawRequestDelegate;
import com.openfeint.internal.request.JSONRequest;
import com.openfeint.internal.request.OrderedArgList;
import com.openfeint.internal.request.RawRequest;
import com.openfeint.internal.request.multipart.ByteArrayPartSource;
import com.openfeint.internal.request.multipart.FilePartSource;
import com.openfeint.internal.request.multipart.PartSource;
import com.openfeint.internal.resource.BlobUploadParameters;
import com.openfeint.internal.resource.ServerException;
import com.openfeint.internal.ui.IntroFlow;
import com.openfeint.internal.ui.WebViewCache;
public class OpenFeintInternal {
// Class constants
private static final String TAG = "OpenFeint";
private static final boolean DEVELOPMENT_LOGGING_ENABLED = false;
// Static members
private static OpenFeintInternal sInstance;
// Members that need serialization
private CurrentUser mCurrentUser;
Client mClient;
private boolean mCurrentlyLoggingIn;
private boolean mCreatingDeviceSession;
private boolean mDeviceSessionCreated;
private boolean mBanned;
private boolean mApproved;
private boolean mDeclined;
// Members that don't need serialization
// nb: we're basically using this as a flag to prevent us from deserializing from a bundle when we're already set up.
// it sure would be nice if android let the Application serialize stuff to a bundle instead of just the Activities!
private boolean mDeserializedAlready;
Handler mMainThreadHandler;
OpenFeintDelegate mDelegate;
OpenFeintSettings mSettings;
Analytics analytics;
private SyncedStore mPrefs;
private Runnable mPostDeviceSessionRunnable;
private List<Runnable> mQueuedPostDeviceSessionRunnables; // These are requests to run after device session, so there can be many
private Runnable mPostLoginRunnable; // This is what intent to launch after login, so there should only be one
private List<Runnable> mQueuedPostLoginRunnables; // These are requests to run after login, so there can be many
String mUDID;
String mAppVersion;
Properties mInternalProperties;
String mServerUrl;
private Context mContext;
private LoginDelegate mLoginDelegate;
public void setLoginDelegate(LoginDelegate delegate) {
mLoginDelegate = delegate;
}
public interface LoginDelegate {
public void login(User currentUser);
}
private void _saveInstanceState(Bundle outState) {
if (mCurrentUser != null) outState.putString("mCurrentUser", mCurrentUser.generate());
if (mClient != null) mClient.saveInstanceState(outState);
outState.putBoolean("mCurrentlyLoggingIn", mCurrentlyLoggingIn);
outState.putBoolean("mCreatingDeviceSession", mCreatingDeviceSession);
outState.putBoolean("mDeviceSessionCreated", mDeviceSessionCreated);
outState.putBoolean("mBanned", mBanned);
outState.putBoolean("mApproved", mApproved);
outState.putBoolean("mDeclined", mDeclined);
}
private void _restoreInstanceState(Bundle inState) {
if (!mDeserializedAlready && inState != null) {
mCurrentUser = (CurrentUser)userFromString(inState.getString("mCurrentUser"));
if (mClient != null) mClient.restoreInstanceState(inState);
mCurrentlyLoggingIn = inState.getBoolean("mCurrentlyLoggingIn");
mCreatingDeviceSession = inState.getBoolean("mCreatingDeviceSession");
mDeviceSessionCreated = inState.getBoolean("mDeviceSessionCreated");
mBanned = inState.getBoolean("mBanned");
mApproved = inState.getBoolean("mApproved");
mDeclined = inState.getBoolean("mDeclined");
mDeserializedAlready = true;
}
}
public static void saveInstanceState(Bundle outState) {
getInstance()._saveInstanceState(outState);
}
public static void restoreInstanceState(Bundle inState) {
getInstance()._restoreInstanceState(inState);
}
// Hey psuedo-singleton
public static OpenFeintInternal getInstance() { return sInstance; }
public OpenFeintDelegate getDelegate() { return mDelegate; }
public AbstractHttpClient getClient() { return mClient; }
public SyncedStore getPrefs() {
if (mPrefs == null) {
mPrefs = new SyncedStore(getContext());
}
return mPrefs;
}
private void saveUser(SyncedStore.Editor e, User u) {
e.putString("last_logged_in_user", u.generate());
}
private void clearUser(SyncedStore.Editor e) {
e.remove("last_logged_in_user");
}
private User loadUser() {
String urep = null;
SyncedStore.Reader r = getPrefs().read();
try {
urep = r.getString("last_logged_in_user", null);
} finally {
r.complete();
}
return userFromString(urep);
}
private static User userFromString(String urep) {
if (urep == null) return null;
try {
JsonFactory jsonFactory = new JsonFactory(); // A throwaway factory.
JsonParser jp = jsonFactory.createJsonParser(new ByteArrayInputStream(urep.getBytes()));
JsonResourceParser jrp = new JsonResourceParser(jp);
Object responseBody = jrp.parse();
if (responseBody != null && responseBody instanceof User) {
return (User)responseBody;
}
} catch (IOException e) {
}
return null;
}
private void userLoggedIn(User loggedInUser) {
mCurrentUser = new CurrentUser();
mCurrentUser.shallowCopyAncestorType(loggedInUser);
SyncedStore.Editor e = getPrefs().edit();
try {
e.putString("last_logged_in_server", getServerUrl());
saveUserApproval(e);
saveUser(e, loggedInUser);
} finally {
e.commit();
}
if (mDelegate != null) {
mDelegate.userLoggedIn(mCurrentUser);
}
if (mLoginDelegate != null) {
mLoginDelegate.login(mCurrentUser);
}
if (mPostLoginRunnable != null) {
mMainThreadHandler.post(mPostLoginRunnable);
mPostLoginRunnable = null;
}
getAnalytics().markSessionOpen(true);
AchievementUnlockCache.reset();
}
private void userLoggedOut() {
User previousLocalUser = mCurrentUser;
mCurrentUser = null;
mDeviceSessionCreated = false;
clearPrefs();
if (mDelegate != null) {
mDelegate.userLoggedOut(previousLocalUser);
}
getAnalytics().markSessionClose();
}
private void clearPrefs() {
SyncedStore.Editor e = getPrefs().edit();
try {
e.remove("last_logged_in_server");
e.remove("last_logged_in_user_name");
clearUser(e);
} finally {
e.commit();
}
}
public void createUser(final String userName, final String email, final String password, final String passwordConfirmation, final IRawRequestDelegate delegate) {
OrderedArgList bootstrapArgs = new OrderedArgList();
bootstrapArgs.put("platform", "android");
bootstrapArgs.put("of-version", getOFVersion());
bootstrapArgs.put("app-version", getAppVersion());
bootstrapArgs.put("user[name]", userName);
bootstrapArgs.put("user[http_basic_credential_attributes][email]", email);
bootstrapArgs.put("user[http_basic_credential_attributes][password]", password);
bootstrapArgs.put("user[http_basic_credential_attributes][password_confirmation]", passwordConfirmation);
RawRequest userCreate = new RawRequest(bootstrapArgs) {
@Override public String method() { return "POST"; }
@Override public String path() { return "/xp/users.json"; }
@Override public void onSuccess(Object responseBody) {
userLoggedIn((User)responseBody);
}
};
userCreate.setDelegate(delegate);
_makeRequest(userCreate);
}
public static String getModelString() {
return "p(" + android.os.Build.PRODUCT + ")/m(" + android.os.Build.MODEL + ")";
}
public static String getOSVersionString() {
return "v" + android.os.Build.VERSION.RELEASE + " (" + android.os.Build.VERSION.INCREMENTAL + ")";
}
public static String getScreenInfo() {
DisplayMetrics metrics = Util.getDisplayMetrics();
return String.format("%dx%d (%f dpi)", metrics.widthPixels, metrics.heightPixels, metrics.density);
}
public static String getProcessorInfo() {
String family = "unknown";
try {
for (String l : cat("/proc/cpuinfo").split("\n")) {
if (l.startsWith("Processor\t")) {
family = l.split(":")[1].trim();
break;
}
}
} catch (Exception e) {
// Johnny can't parse
}
return String.format("family(%s) min(%s) max(%s)",
family,
cat("/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_min_freq").split("\n")[0],
cat("/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq").split("\n")[0]);
}
private static String cat(String filename) {
FileInputStream f;
try {
f = new FileInputStream(filename);
BufferedReader br = new BufferedReader(new InputStreamReader(f), 8192);
StringBuilder sb = new StringBuilder();
String line = null;
while ((line = br.readLine()) != null) {
sb.append(line + "\n");
}
br.close();
return sb.toString();
} catch (Exception e) {
// d'oh
}
return "unknown";
}
public Map<String, Object> getDeviceParams() {
HashMap<String,Object> device = new HashMap<String,Object>();
device.put("identifier", getUDID());
device.put("hardware", getModelString());
device.put("os", getOSVersionString());
device.put("screen_resolution", getScreenInfo());
device.put("processor", getProcessorInfo());
return device;
}
public void createDeviceSession() {
if (mCreatingDeviceSession || mDeviceSessionCreated) return; // not necessary
HashMap<String,Object> argMap = new HashMap<String,Object>();
argMap.put("platform", "android");
argMap.put("device", getDeviceParams());
OrderedArgList args = new OrderedArgList(argMap);
mCreatingDeviceSession = true;
// NB: this is signed, and NEEDS to be signed, so that the game session will be created
// without being stepped on by concurrent requests.
RawRequest deviceSession = new RawRequest(args) {
@Override public String method() { return "POST"; }
@Override public String path() { return "/xp/devices"; }
@Override public boolean needsDeviceSession() { return false; }
@Override public void onResponse(int responseCode, Object responseBody) {
mCreatingDeviceSession = false;
if (200 <= responseCode && responseCode < 300) {
mDeviceSessionCreated = true;
if (mPostDeviceSessionRunnable != null) {
log(TAG, "Launching post-device-session runnable now.");
mMainThreadHandler.post(mPostDeviceSessionRunnable);
}
} else {
mPostLoginRunnable = null;
showOfflineNotification(responseCode, responseBody);
}
// the queued ones run regardless.
if (mQueuedPostDeviceSessionRunnables != null) for (Runnable r : mQueuedPostDeviceSessionRunnables) {
mMainThreadHandler.post(r);
}
mPostDeviceSessionRunnable = null;
mQueuedPostDeviceSessionRunnables = null;
}
};
_makeRequest(deviceSession);
}
public final void runOnUiThread(Runnable action) {
mMainThreadHandler.post(action);
}
public void loginUser(final String userName, final String password, final String userID, final IRawRequestDelegate delegate) {
if (checkBan()) return;
if (mCreatingDeviceSession || !mDeviceSessionCreated) {
if (!mCreatingDeviceSession) {
createDeviceSession();
}
// just login when we're done.
log(TAG, "No device session yet - queueing login.");
mPostDeviceSessionRunnable = new Runnable() {
public void run() {
loginUser(userName, password, userID, delegate);
}
};
return;
}
boolean allowToast = true;
OrderedArgList bootstrapArgs = new OrderedArgList();
bootstrapArgs.put("platform", "android");
if (userName != null && password != null) {
bootstrapArgs.put("login", userName);
bootstrapArgs.put("password", password);
allowToast = false;
}
// For returning users we need to use the user's id instead of
// login because when Emoji names are used they're sent wrongly
// to the server by the webview somehow.
if (userID != null && password != null){
bootstrapArgs.put("user_id", userID);
bootstrapArgs.put("password", password);
allowToast = false;
}
bootstrapArgs.put("of-version", getOFVersion());
bootstrapArgs.put("app-version", getAppVersion());
mCurrentlyLoggingIn = true;
final boolean finalToast = allowToast;
RawRequest userLogin = new RawRequest(bootstrapArgs) {
@Override public String method() { return "POST"; }
@Override public String path() { return "/xp/sessions.json"; }
@Override public void onResponse(int responseCode, Object responseBody) {
mCurrentlyLoggingIn = false;
if (200 <= responseCode && responseCode < 300) {
userLoggedIn((User)responseBody);
// The singular one only runs if there was a successful login.
if (mPostLoginRunnable != null) {
log(TAG, "Launching post-login runnable now.");
mMainThreadHandler.post(mPostLoginRunnable);
}
} else {
if (finalToast) {
showOfflineNotification(responseCode, responseBody);
}
}
// the queued ones run regardless.
if (mQueuedPostLoginRunnables != null) for (Runnable r : mQueuedPostLoginRunnables) {
mMainThreadHandler.post(r);
}
mPostLoginRunnable = null;
mQueuedPostLoginRunnables = null;
}
};
userLogin.setDelegate(delegate);
_makeRequest(userLogin);
}
public void submitIntent(final Intent intent, final boolean spotlight) {
// I know this seems a strange place to put it, but
// this is the main user-activated entrypoint to OF.
// If we get here, that means we are undeclining, if
// we've actually declined (but don't erase the setting
// yet - it'll get erased if they actually log in).
mDeclined = false;
final Runnable r = new Runnable() {
public void run() {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
getContext().startActivity(intent);
}
};
if (!isUserLoggedIn()) {
log(TAG, "Not logged in yet - queueing intent "+intent.toString()+" for now.");
mPostLoginRunnable = r;
if (!currentlyLoggingIn()) {
login(spotlight);
}
} else {
mMainThreadHandler.post(r); // main thread plz
}
}
public void logoutUser(final IRawRequestDelegate delegate) {
OrderedArgList bootstrapArgs = new OrderedArgList();
bootstrapArgs.put("platform", "android");
RawRequest userLogout = new RawRequest(bootstrapArgs) {
@Override public String method() { return "DELETE"; }
@Override public String path() { return "/xp/sessions.json"; }
};
userLogout.setDelegate(delegate);
_makeRequest(userLogout);
// clear our local stuff immediately, don't wait for req to complete.
userLoggedOut();
}
public static void genericRequest(final String path, final String method, final Map<String, Object> args, final Map<String, Object> httpParams, final IRawRequestDelegate delegate) {
makeRequest(new GenericRequest(path, method, args, httpParams, delegate));
}
public void userApprovedFeint() {
mApproved = true;
mDeclined = false;
SyncedStore.Editor e = getPrefs().edit();
try {
saveUserApproval(e);
} finally {
e.commit();
}
launchIntroFlow(false);
}
private void saveUserApproval(SyncedStore.Editor e) {
e.remove(getContext().getPackageName() + ".of_declined");
}
public void userDeclinedFeint() {
mApproved = false;
mDeclined = true;
SyncedStore.Editor e = getPrefs().edit();
try {
e.putString(getContext().getPackageName() + ".of_declined", "sadly");
} finally {
e.commit();
}
}
public boolean currentlyLoggingIn() {
return mCurrentlyLoggingIn || mCreatingDeviceSession;
}
public CurrentUser getCurrentUser() { return mCurrentUser; }
public boolean isUserLoggedIn() { return getCurrentUser() != null; }
public String getUDID() {
if (mUDID == null) {
mUDID = findUDID();
}
return mUDID;
}
public Properties getInternalProperties() { return mInternalProperties; }
public String getServerUrl() {
if(mServerUrl == null){
// load and normalize url
String raw = getInternalProperties().getProperty("server-url").toLowerCase().trim();
if(raw.endsWith("/")) {
mServerUrl = raw.substring(0, raw.length() - 1);
} else {
mServerUrl = raw;
}
}
return mServerUrl;
}
public String getOFVersion() { return getInternalProperties().getProperty("of-version"); }
public String getAppName() { return mSettings.name; }
public String getAppID() { return mSettings.id; }
public Map<String, Object> getSettings() { return mSettings.settings; }
public String getAppVersion() {
if (mAppVersion == null) {
Context c = getContext();
try {
PackageInfo p = c.getPackageManager().getPackageInfo(c.getPackageName(), 0);
mAppVersion = p.versionName;
} catch (Exception e) {
mAppVersion = "1.0";
}
}
return mAppVersion;
}
public Context getContext() { return mContext; }
public void displayErrorDialog(final CharSequence errorMessage) {
mMainThreadHandler.post(new Runnable() {
public void run() {
new AlertDialog.Builder(getContext())
.setMessage(errorMessage)
.setNegativeButton(OpenFeintInternal.getRString(R.string.of_ok), null)
.show();
}
});
}
private String findUDID() {
String androidID = android.provider.Settings.Secure.getString(getContext().getContentResolver(), android.provider.Settings.Secure.ANDROID_ID);
// If there's no android ID, or if it's the magic universal 2.2 emulator ID, we need to generate one.
if (androidID != null && !androidID.equals("9774d56d682e549c")) {
return "android-id-" + androidID;
} else {
// We're in an emulator.
SyncedStore.Reader r = getPrefs().read();
try {
androidID = r.getString("udid", null);
} finally {
r.complete();
}
if (androidID == null) {
byte randomBytes[] = new byte[16];
new Random().nextBytes(randomBytes);
androidID = "android-emu-" + new String(Hex.encodeHex(randomBytes)).replace("\r\n", "");
SyncedStore.Editor e = getPrefs().edit();
try {
e.putString("udid", androidID);
} finally {
e.commit();
}
}
return androidID;
}
}
public static void makeRequest(final BaseRequest req) {
OpenFeintInternal ofi = getInstance();
if (ofi == null) {
ServerException e = new ServerException();
e.exceptionClass = "NoFeint";
e.message = "OpenFeint has not been initialized.";
req.onResponse(0, e.generate().getBytes());
} else {
ofi._makeRequest(req);
}
}
private final void _makeRequest(final BaseRequest req) {
if (!isUserLoggedIn() && req.wantsLogin() && lastLoggedInUser() != null && isFeintServerReachable()) {
// make sure we're logging in.
login(false);
if (mQueuedPostLoginRunnables == null) {
mQueuedPostLoginRunnables = new ArrayList<Runnable>();
}
mQueuedPostLoginRunnables.add(new Runnable() {
public void run() {
mClient.makeRequest(req);
}
});
} else if (!mDeviceSessionCreated && req.needsDeviceSession()) {
// make sure it's in progress, at least.
createDeviceSession();
if (mQueuedPostDeviceSessionRunnables == null) {
mQueuedPostDeviceSessionRunnables = new ArrayList<Runnable>();
}
mQueuedPostDeviceSessionRunnables.add(new Runnable() {
public void run() {
mClient.makeRequest(req);
}
});
} else {
mClient.makeRequest(req);
}
}
public interface IUploadDelegate {
public void fileUploadedTo(String url, boolean success);
}
public void uploadFile(final String xpApiPath, final String filePath, final String contentType, final IUploadDelegate delegate) {
try {
String fileName = filePath;
String[] parts = filePath.split("/");
if (parts.length > 0) {
fileName = parts[parts.length-1];
}
uploadFile(xpApiPath, new FilePartSource(fileName, new File(filePath)), contentType, delegate);
} catch (FileNotFoundException e) {
delegate.fileUploadedTo("", false);
}
}
public void uploadFile(final String xpApiPath, final String fileName, final byte[] fileData, final String contentType, final IUploadDelegate delegate) {
uploadFile(xpApiPath, new ByteArrayPartSource(fileName, fileData), contentType, delegate);
}
public void uploadFile(final String xpApiPath, final PartSource partSource, final String contentType, final IUploadDelegate delegate) {
JSONRequest xpRequest = new JSONRequest() {
@Override public boolean wantsLogin() { return true; }
@Override public String method() { return "POST"; }
@Override public String path() { return xpApiPath; }
@Override public void onSuccess(Object responseBody) {
final BlobUploadParameters params = (BlobUploadParameters)responseBody;
BlobPostRequest bp = new BlobPostRequest(params, partSource, contentType);
if (delegate != null) {
bp.setDelegate(new IRawRequestDelegate() {
public void onResponse(int responseCode, String responseBody) {
delegate.fileUploadedTo(params.action + params.key, (200 <= responseCode && responseCode < 300));
}
});
}
_makeRequest(bp);
}
@Override public void onFailure(String reason) {
if (delegate != null) { delegate.fileUploadedTo("", false); }
}
};
_makeRequest(xpRequest);
}
public int getResource(String resourceName) {
String packageName = getContext().getPackageName();
return getContext().getResources().getIdentifier(resourceName, null, packageName);
}
public static String getRString(int id) {
final OpenFeintInternal ofi = getInstance();
final Context ctx = ofi.getContext();
return ctx.getResources().getString(id);
}
public static void initializeWithoutLoggingIn(Context ctx, OpenFeintSettings settings, OpenFeintDelegate delegate) {
// Check permissions and stuff.
if (!validateManifest(ctx)) return;
if (sInstance == null) {
sInstance = new OpenFeintInternal(settings, ctx);
}
sInstance.mDelegate = delegate;
if (!sInstance.mDeclined) {
sInstance.createDeviceSession();
}
}
public static void initialize(Context ctx, OpenFeintSettings settings, OpenFeintDelegate delegate) {
initializeWithoutLoggingIn(ctx, settings, delegate);
final OpenFeintInternal ofi = getInstance();
if (ofi != null) {
ofi.login(false);
}
}
public void setDelegate(OpenFeintDelegate delegate) {
mDelegate = delegate;
}
private static boolean validateManifest(Context appContext) {
// Check ActivityInfo.
final PackageManager packageManager = appContext.getPackageManager();
try {
final PackageInfo packageInfo = packageManager.getPackageInfo(appContext.getPackageName(), PackageManager.GET_ACTIVITIES);
final String neededActivityInfo[] = new String[] {
"com.openfeint.api.ui.Dashboard",
"com.openfeint.internal.ui.IntroFlow",
"com.openfeint.internal.ui.Settings",
"com.openfeint.internal.ui.NativeBrowser",
};
for (String n : neededActivityInfo) {
boolean victory = false;
for (ActivityInfo ai : packageInfo.activities) {
if (ai.name.equals(n)) {
if (ai.configChanges != (ActivityInfo.CONFIG_ORIENTATION | ActivityInfo.CONFIG_KEYBOARD_HIDDEN))
{
Log.v(TAG, String.format("ActivityInfo for %s has the wrong configChanges.\nPlease consult README.txt for the correct configuration.", n));
return false;
}
victory = true;
break;
}
}
if (!victory) {
Log.v(TAG, String.format("Couldn't find ActivityInfo for %s.\nPlease consult README.txt for the correct configuration.", n));
return false;
}
}
// check permissions.
final String neededPermissionInfo[] = new String[] {
android.Manifest.permission.INTERNET,
};
for (String n : neededPermissionInfo) {
if (Util.noPermission(n, appContext, packageManager)) {
// we don't have this permission, but it's in the manifest - user must have refused it.
// Don't bitch, but don't construct OF either.
return false;
}
}
} catch (NameNotFoundException e) {
Log.v(TAG, String.format("Couldn't find PackageInfo for %s.\nPlease initialize OF with an Activity that lives in your root package.", appContext.getPackageName()));
return false;
}
return true;
}
private OpenFeintInternal(OpenFeintSettings settings, final Context ctx) {
sInstance = this;
mContext = ctx;
mSettings = settings;
SyncedStore.Reader r = getPrefs().read();
try {
mDeclined = (r.getString(getContext().getPackageName() + ".of_declined", null) != null);
} finally {
r.complete();
}
mMainThreadHandler = new Handler();
mInternalProperties = new Properties();
mInternalProperties.put("server-url", "https://api.openfeint.com");
mInternalProperties.put("of-version", "1.7.1");
loadPropertiesFromXMLResource(mInternalProperties, getResource("@xml/openfeint_internal_settings"));
Log.i(TAG, "Using OpenFeint version " + mInternalProperties.get("of-version") + " (" + mInternalProperties.get("server-url") + ")");
Properties appProperties = new Properties();
loadPropertiesFromXMLResource(appProperties, getResource("@xml/openfeint_app_settings"));
mSettings.applyOverrides(appProperties);
mSettings.verify();
mClient = new Client(mSettings.key, mSettings.secret, getPrefs());
Util.moveWebCache(ctx);
WebViewCache.initialize(ctx);
// create db after WebViewCache.initialize and before WebViewCache.start
// so that we able to copy pre 1.5 db to sdcard
DB.createDB(ctx);
WebViewCache.start();
analytics = new Analytics();
}
public Analytics getAnalytics() { return analytics; }
public String getUserID() {
User user = getCurrentUser();
if (user != null) return user.userID();
user = lastLoggedInUser();
if (user != null) return user.userID();
return null;
}
private final User lastLoggedInUser() {
final User savedUser = loadUser();
SyncedStore.Reader r = getPrefs().read();
try {
final URL saved = new URL(getServerUrl());
final URL loaded = new URL(r.getString("last_logged_in_server", ""));
if (savedUser != null && saved.equals(loaded))
return savedUser;
} catch (MalformedURLException e) {
} finally {
r.complete();
}
return null;
}
public void login(final boolean spotlight) {
// for safety, -always- delay this one tick. We might get initialize()d, which fires off a login(),
// but if we're resuming from saved state, that'll deserialize a logged-in user, so at that point
// we'll want to bail on the login process.
final Runnable r = new Runnable() {
public void run() {
if (mDeclined || mCurrentlyLoggingIn || isUserLoggedIn()) {
return;
}
// At this point, even though we aren't 'deserialized' per se, we are committed to logging in,
// so deserialization should not be allowed to happen after this point.
mDeserializedAlready = true;
final User savedUser = lastLoggedInUser();
if (savedUser != null) {
log(TAG, "Logging in last known user: "+ savedUser.name);
loginUser(null, null, null, new IRawRequestDelegate() {
public void onResponse(int responseCode, String responseBody) {
if (!(200 <= responseCode && responseCode < 300)) {
if (403 == responseCode) {
// This is a banned user or game. Don't launch the intro flow.
mBanned = true;
} else {
// Oops, there was a login failure - launch the normal intro flow.
launchIntroFlow(spotlight);
}
}
else {
SimpleNotification.show("Welcome back " + savedUser.name, Category.Login, Type.Success);
}
}
});
} else {
log(TAG, "No last user, launch intro flow");
clearPrefs();
launchIntroFlow(spotlight);
}
}
};
mMainThreadHandler.post(r);
}
private boolean checkBan() {
if (mBanned) {
displayErrorDialog(getContext().getText(R.string.of_banned_dialog));
return true;
}
return false;
}
public void launchIntroFlow(final boolean spotlight) {
if (checkBan()) return;
if(isFeintServerReachable()) {
OpenFeintDelegate d = getDelegate();
if (!mApproved && d != null && d.showCustomApprovalFlow(getContext())) {
// Oh, we should wait on this to finish.
} else {
// We've either been custom-approved already, or there's no custom flow.
final Runnable r = new Runnable() {
public void run() {
final Intent i = new Intent(getContext(), IntroFlow.class);
if (mApproved && spotlight) {
i.putExtra("content_name", "index?preapproved=true&spotlight=true");
} else if(spotlight) {
i.putExtra("content_name", "index?spotlight=true");
} else if(mApproved) {
i.putExtra("content_name", "index?preapproved=true");
}
i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
getContext().startActivity(i);
}
};
if (mCreatingDeviceSession || !mDeviceSessionCreated) {
if (!mCreatingDeviceSession) {
createDeviceSession();
}
mPostDeviceSessionRunnable = r;
} else {
r.run();
}
}
}
else {
showOfflineNotification(0, "");
}
}
private void showOfflineNotification(int httpCode, Object responseBody) {
Resources r = getContext().getResources();
String serverMessage = r.getString(R.string.of_offline_notification_line2);
if (0 != httpCode) {
if (403 == httpCode) {
mBanned = true;
}
if (responseBody instanceof ServerException) {
serverMessage = ((ServerException)responseBody).message;
}
}
TwoLineNotification.show(r.getString(R.string.of_offline_notification),
serverMessage,
Notification.Category.Foreground, Notification.Type.NetworkOffline);
log("Reachability", "Unable to launch IntroFlow because: " + serverMessage);
}
private void loadPropertiesFromXMLResource(Properties defaults, int resourceID) {
XmlResourceParser xml = null;
try {
xml = getContext().getResources().getXml(resourceID);
} catch (Exception e) {
}
if (xml != null) {
// terrible parser courtesy of it being Thursday
try {
String k = null;
for (int eventType = xml.getEventType(); xml.getEventType() != XmlPullParser.END_DOCUMENT; xml.next(), eventType = xml.getEventType()) {
if (eventType == XmlPullParser.START_TAG) {
k = xml.getName();
} else if (xml.getEventType() == XmlPullParser.TEXT) {
defaults.setProperty(k, xml.getText());
}
}
} catch (Exception e) {
throw new RuntimeException(e);
}
xml.close();
}
}
public boolean isFeintServerReachable() {
ConnectivityManager conMan = (ConnectivityManager) getContext().getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo activeNetwork = conMan.getActiveNetworkInfo();
return activeNetwork != null && activeNetwork.isConnected();
}
public static void log(String tag, String message) {
if (DEVELOPMENT_LOGGING_ENABLED) {
Log.v(tag, message != null ? message : "(null)");
}
}
}