package com.jdroid.android.application;
import android.app.Activity;
import android.app.Application;
import android.content.Context;
import android.content.res.Configuration;
import android.os.StrictMode;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.WorkerThread;
import android.support.v4.app.Fragment;
import com.jdroid.android.R;
import com.jdroid.android.activity.AbstractFragmentActivity;
import com.jdroid.android.activity.ActivityHelper;
import com.jdroid.android.activity.ActivityLifecycleHandler;
import com.jdroid.android.analytics.AnalyticsSender;
import com.jdroid.android.analytics.AnalyticsTracker;
import com.jdroid.android.context.AndroidGitContext;
import com.jdroid.android.context.AppContext;
import com.jdroid.android.debug.DebugContext;
import com.jdroid.android.exception.DefaultExceptionHandler;
import com.jdroid.android.exception.ExceptionHandler;
import com.jdroid.android.firebase.remoteconfig.RemoteConfigParameter;
import com.jdroid.android.fragment.FragmentHelper;
import com.jdroid.android.http.cache.CacheManager;
import com.jdroid.android.images.loader.ImageLoaderHelper;
import com.jdroid.android.images.loader.uil.UilImageLoaderHelper;
import com.jdroid.android.repository.UserRepository;
import com.jdroid.android.sqlite.SQLiteHelper;
import com.jdroid.android.sqlite.SQLiteUpgradeStep;
import com.jdroid.android.uri.UriMapper;
import com.jdroid.android.utils.AppUtils;
import com.jdroid.android.utils.SharedPreferencesHelper;
import com.jdroid.android.utils.ToastUtils;
import com.jdroid.java.collections.Lists;
import com.jdroid.java.collections.Maps;
import com.jdroid.java.concurrent.ExecutorUtils;
import com.jdroid.java.context.GitContext;
import com.jdroid.java.date.DateUtils;
import com.jdroid.java.domain.Identifiable;
import com.jdroid.java.http.HttpServiceFactory;
import com.jdroid.java.repository.Repository;
import com.jdroid.java.utils.LoggerUtils;
import com.jdroid.java.utils.ReflectionUtils;
import com.jdroid.java.utils.StringUtils;
import org.slf4j.Logger;
import java.lang.Thread.UncaughtExceptionHandler;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import io.fabric.sdk.android.Fabric;
import io.fabric.sdk.android.Kit;
public abstract class AbstractApplication extends Application {
/**
* The LOGGER variable is initialized in the "OnCreate" method, after that "LoggerUtils" has been properly
* configured by the superclass.
*/
protected static Logger LOGGER;
private static final String INSTALLATION_SOURCE = "installationSource";
private static final String VERSION_CODE_KEY = "versionCodeKey";
protected static AbstractApplication INSTANCE;
private AppContext appContext;
private GitContext gitContext;
private DebugContext debugContext;
private AnalyticsSender<? extends AnalyticsTracker> analyticsSender;
private UriMapper uriMapper;
/** Current activity in the top stack. */
private Activity currentActivity;
private AppLaunchStatus appLaunchStatus;
private Map<Class<? extends Identifiable>, Repository<? extends Identifiable>> repositories;
private Map<String, AppModule> appModulesMap = Maps.newLinkedHashMap();
private ImageLoaderHelper imageLoaderHelper;
private UpdateManager updateManager;
private CacheManager cacheManager;
private String installationSource;
private UncaughtExceptionHandler defaultAndroidExceptionHandler = Thread.getDefaultUncaughtExceptionHandler();
private ActivityLifecycleHandler activityLifecycleHandler;
private List<RemoteConfigParameter> remoteConfigParameters;
private HttpServiceFactory httpServiceFactory;
public AbstractApplication() {
INSTANCE = this;
}
public static AbstractApplication get() {
return INSTANCE;
}
/**
* @see android.app.Application#onCreate()
*/
@Override
public void onCreate() {
super.onCreate();
appContext = createAppContext();
LoggerUtils.setEnabled(appContext.isLoggingEnabled());
LOGGER = LoggerUtils.getLogger(AbstractApplication.class);
LOGGER.debug("Executing onCreate on " + this);
// Strict mode
if (appContext.isStrictModeEnabled()) {
initStrictMode();
}
initAppModule(appModulesMap);
remoteConfigParameters = createRemoteConfigParameters();
List<Kit> fabricKits = Lists.newArrayList();
for (AppModule each: appModulesMap.values()) {
each.onCreate();
fabricKits.addAll(each.getFabricKits());
}
fabricKits.addAll(getFabricKits());
if (!fabricKits.isEmpty()) {
Fabric.with(this, fabricKits.toArray(new Kit[0]));
}
analyticsSender = createAnalyticsSender(createAnalyticsTrackers());
uriMapper = createUriMapper();
updateManager = createUpdateManager();
initExceptionHandlers();
LoggerUtils.setExceptionLogger(getExceptionHandler());
// This is required to initialize the statics fields of the utils classes.
ToastUtils.init();
DateUtils.init();
imageLoaderHelper = createImageLoaderHelper();
initRepositories();
ExecutorUtils.execute(new Runnable() {
@Override
public void run() {
fetchInstallationSource();
verifyAppLaunchStatus();
if (getCacheManager() != null) {
getCacheManager().initFileSystemCache();
}
if (imageLoaderHelper != null) {
imageLoaderHelper.init();
}
}
});
activityLifecycleHandler = new ActivityLifecycleHandler();
registerActivityLifecycleCallbacks(activityLifecycleHandler);
}
@Nullable
public ImageLoaderHelper getImageLoaderHelper() {
return imageLoaderHelper;
}
@Nullable
protected ImageLoaderHelper createImageLoaderHelper() {
return new UilImageLoaderHelper();
}
protected void initAppModule(Map<String, AppModule> appModulesMap) {
// Do nothing
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
for (AppModule each: appModulesMap.values()) {
each.onConfigurationChanged(newConfig);
}
}
@Override
public void onLowMemory() {
super.onLowMemory();
for (AppModule each: appModulesMap.values()) {
each.onLowMemory();
}
}
@Override
public void onTrimMemory(int level) {
super.onTrimMemory(level);
for (AppModule each: appModulesMap.values()) {
each.onTrimMemory(level);
}
}
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
for (AppModule each: appModulesMap.values()) {
each.attachBaseContext(base);
}
}
protected void verifyAppLaunchStatus() {
Integer fromVersionCode = SharedPreferencesHelper.get().loadPreferenceAsInteger(VERSION_CODE_KEY);
if (fromVersionCode == null) {
appLaunchStatus = AppLaunchStatus.NEW_INSTALLATION;
} else {
if (AppUtils.getVersionCode().equals(fromVersionCode)) {
appLaunchStatus = AppLaunchStatus.NORMAL;
} else {
appLaunchStatus = AppLaunchStatus.VERSION_UPGRADE;
}
}
LOGGER.debug("App launch status: " + appLaunchStatus);
if (!appLaunchStatus.equals(AppLaunchStatus.NORMAL)) {
SharedPreferencesHelper.get().savePreferenceAsync(VERSION_CODE_KEY, AppUtils.getVersionCode());
}
if (appLaunchStatus.equals(AppLaunchStatus.VERSION_UPGRADE) && updateManager != null) {
updateManager.update(fromVersionCode);
}
}
@NonNull
protected AnalyticsSender<? extends AnalyticsTracker> createAnalyticsSender(List<? extends AnalyticsTracker> analyticsTrackers) {
return new AnalyticsSender<>(analyticsTrackers);
}
public List<? extends AnalyticsTracker> createAnalyticsTrackers() {
List<AnalyticsTracker> analyticsTrackers = Lists.newArrayList();
for (AppModule each: appModulesMap.values()) {
analyticsTrackers.addAll(each.getAnalyticsTrackers());
}
return analyticsTrackers;
}
@SuppressWarnings("unchecked")
@NonNull
public AnalyticsSender<? extends AnalyticsTracker> getAnalyticsSender() {
return analyticsSender;
}
protected void initStrictMode() {
StrictMode.enableDefaults();
LOGGER.info("StrictMode initialized");
}
public void initExceptionHandlers() {
Class<? extends ExceptionHandler> exceptionHandlerClass = getExceptionHandlerClass();
if (!Thread.getDefaultUncaughtExceptionHandler().getClass().equals(exceptionHandlerClass)) {
getAnalyticsSender().onInitExceptionHandler(getExceptionHandlerMetadata());
ExceptionHandler exceptionHandler = ReflectionUtils.newInstance(exceptionHandlerClass);
exceptionHandler.setDefaultExceptionHandler(defaultAndroidExceptionHandler);
Thread.setDefaultUncaughtExceptionHandler(exceptionHandler);
LOGGER.info(exceptionHandlerClass.getSimpleName() + " initialized");
}
}
protected Map<String, String> getExceptionHandlerMetadata() {
return null;
}
public ExceptionHandler getExceptionHandler() {
return (ExceptionHandler)Thread.getDefaultUncaughtExceptionHandler();
}
public Class<? extends ExceptionHandler> getExceptionHandlerClass() {
return DefaultExceptionHandler.class;
}
@WorkerThread
public String getInstallationSource() {
if (installationSource == null) {
fetchInstallationSource();
}
return installationSource;
}
private synchronized void fetchInstallationSource() {
installationSource = SharedPreferencesHelper.get().loadPreference(INSTALLATION_SOURCE);
if (StringUtils.isBlank(installationSource)) {
installationSource = appContext.getInstallationSource();
SharedPreferencesHelper.get().savePreference(INSTALLATION_SOURCE, installationSource);
}
}
public abstract Class<? extends Activity> getHomeActivityClass();
@NonNull
protected abstract AppContext createAppContext();
@NonNull
public AppContext getAppContext() {
return appContext;
}
@Nullable
protected UpdateManager createUpdateManager() {
return null;
}
@Nullable
protected CacheManager createCacheManager() {
return new CacheManager();
}
@Nullable
public CacheManager getCacheManager() {
synchronized (AbstractApplication.class) {
if (cacheManager == null) {
cacheManager = createCacheManager();
}
}
return cacheManager;
}
@NonNull
protected UriMapper createUriMapper() {
return new UriMapper();
}
@NonNull
public UriMapper getUriMapper() {
return uriMapper;
}
@NonNull
protected GitContext createGitContext() {
return new AndroidGitContext();
}
@NonNull
public GitContext getGitContext() {
synchronized (AbstractApplication.class) {
if (gitContext == null) {
gitContext = createGitContext();
}
}
return gitContext;
}
protected DebugContext createDebugContext() {
return new DebugContext();
}
public DebugContext getDebugContext() {
synchronized (AbstractApplication.class) {
if (debugContext == null) {
debugContext = createDebugContext();
}
}
return debugContext;
}
public ActivityHelper createActivityHelper(AbstractFragmentActivity activity) {
return new ActivityHelper(activity);
}
public FragmentHelper createFragmentHelper(Fragment fragment) {
return new FragmentHelper(fragment);
}
public void setCurrentActivity(Activity activity) {
currentActivity = activity;
}
public Activity getCurrentActivity() {
return currentActivity;
}
/**
* @return the inBackground
*/
public Boolean isInBackground() {
return activityLifecycleHandler == null || activityLifecycleHandler.isInBackground();
}
public Boolean isLoadingCancelable() {
return false;
}
public String getAppName() {
return getString(R.string.jdroid_appName);
}
public UserRepository getUserRepository() {
return null;
}
private void initRepositories() {
repositories = new HashMap<>();
initRepositories(repositories);
if (isDatabaseEnabled()) {
SQLiteHelper dbHelper = new SQLiteHelper(this);
getDebugContext().initDebugRepositories(repositories, dbHelper);
initDatabaseRepositories(repositories, dbHelper);
dbHelper.addUpgradeSteps(getSQLiteUpgradeSteps());
}
}
protected void initRepositories(Map<Class<? extends Identifiable>, Repository<? extends Identifiable>> repositories) {
// Do nothing
}
protected void initDatabaseRepositories(
Map<Class<? extends Identifiable>, Repository<? extends Identifiable>> repositories, SQLiteHelper dbHelper) {
// Do nothing
}
public Boolean isDatabaseEnabled() {
return false;
}
protected List<SQLiteUpgradeStep> getSQLiteUpgradeSteps() {
return Lists.newArrayList();
}
public Boolean isDebugLogRepositoryEnabled() {
return false;
}
@SuppressWarnings("unchecked")
public <M extends Identifiable> Repository<M> getRepositoryInstance(Class<M> persistentClass) {
return (Repository<M>)repositories.get(persistentClass);
}
public AppLaunchStatus getAppLaunchStatus() {
return appLaunchStatus;
}
public List<AppModule> getAppModules() {
return Lists.newArrayList(appModulesMap.values());
}
public AppModule getAppModule(String appModuleName) {
return appModulesMap.get(appModuleName);
}
public List<Kit> getFabricKits() {
return Lists.newArrayList();
}
public void initializeGcmTasks() {
for (AppModule each: appModulesMap.values()) {
each.onInitializeGcmTasks();
}
}
public abstract int getLauncherIconResId();
public abstract int getNotificationIconResId();
public abstract String getManifestPackageName();
private List<RemoteConfigParameter> createRemoteConfigParameters() {
List<RemoteConfigParameter> remoteConfigParameters = Lists.newArrayList();
List<RemoteConfigParameter> params = appContext.getRemoteConfigParameters();
if (params != null) {
remoteConfigParameters.addAll(params);
}
for (AppModule each: appModulesMap.values()) {
params = each.getRemoteConfigParameters();
if (params != null) {
remoteConfigParameters.addAll(params);
}
}
return remoteConfigParameters;
}
public List<RemoteConfigParameter> getRemoteConfigParameters() {
return remoteConfigParameters;
}
public void addAppModulesMap(String name, AppModule appModule) {
this.appModulesMap.put(name, appModule);
}
public HttpServiceFactory getHttpServiceFactory() {
return httpServiceFactory;
}
public void setHttpServiceFactory(HttpServiceFactory httpServiceFactory) {
this.httpServiceFactory = httpServiceFactory;
}
public Class<?> getBuildConfigClass() {
return ReflectionUtils.getClass(getManifestPackageName() + ".BuildConfig");
}
}