/*
* Copyright 2015 Hippo Seven
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.hippo.nimingban;
import android.app.ActivityManager;
import android.app.Application;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.net.ConnectivityManager;
import android.os.Debug;
import android.support.annotation.NonNull;
import android.util.Log;
import com.alibaba.fastjson.JSON;
import com.hippo.conaco.Conaco;
import com.hippo.drawable.ImageWrapper;
import com.hippo.nimingban.client.NMBClient;
import com.hippo.nimingban.client.NMBDns;
import com.hippo.nimingban.client.NMBRequest;
import com.hippo.nimingban.client.ac.data.ACCdnPath;
import com.hippo.nimingban.client.data.ACSite;
import com.hippo.nimingban.client.NMBInterceptor;
import com.hippo.nimingban.network.HttpCookieDB;
import com.hippo.nimingban.network.HttpCookieWithId;
import com.hippo.nimingban.network.SimpleCookieStore;
import com.hippo.nimingban.util.BitmapUtils;
import com.hippo.nimingban.util.Crash;
import com.hippo.nimingban.util.DB;
import com.hippo.nimingban.util.ReadableTime;
import com.hippo.nimingban.util.ResImageGetter;
import com.hippo.nimingban.util.Settings;
import com.hippo.nimingban.widget.ImageWrapperHelper;
import com.hippo.okhttp.CookieDBJar;
import com.hippo.util.NetworkUtils;
import com.hippo.yorozuya.FileUtils;
import com.hippo.yorozuya.IOUtils;
import com.hippo.yorozuya.Messenger;
import com.hippo.yorozuya.Say;
import com.hippo.yorozuya.SimpleHandler;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpCookie;
import java.net.URL;
import java.util.List;
import java.util.concurrent.TimeUnit;
import okhttp3.OkHttpClient;
public final class NMBApplication extends Application
implements Thread.UncaughtExceptionHandler, Messenger.Receiver, Runnable {
private static final String TAG = NMBApplication.class.getSimpleName();
private static final boolean LOG_NATIVE_MEMORY = false;
private static final String AC_CDN_PATH_FILENAME = "ac_cdn_path";
private Thread.UncaughtExceptionHandler mDefaultHandler;
private SimpleCookieStore mSimpleCookieStore;
private NMBClient mNMBClient;
private Conaco<ImageWrapper> mConaco;
private ImageWrapperHelper mImageWrapperHelper;
private OkHttpClient mOkHttpClient;
private boolean mConnectedWifi;
@Override
public void onCreate() {
super.onCreate();
// Prepare to crash
mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();
Thread.setDefaultUncaughtExceptionHandler(this);
NMBAppConfig.initialize(this);
File logFile = NMBAppConfig.getFileInAppDir("nimingban.log");
if (logFile != null) {
Say.initSayFile(logFile);
}
Settings.initialize(this);
DB.initialize(this);
HttpCookieDB.initialize(this);
ReadableTime.initialize(this);
BitmapUtils.initialize(this);
ResImageGetter.initialize(this);
Emoji.initialize(this);
// Remove temp file
FileUtils.deleteContent(NMBAppConfig.getTempDir());
// Check network state
updateNetworkState(this);
registerReceiver(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
updateNetworkState(context);
}
}, new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION));
// Theme
setTheme(Settings.getDarkTheme() ? R.style.AppTheme_Dark : R.style.AppTheme);
Messenger.getInstance().register(Constants.MESSENGER_ID_CHANGE_THEME, this);
try {
update();
} catch (PackageManager.NameNotFoundException e) {
// Ignore
}
if (LOG_NATIVE_MEMORY) {
SimpleHandler.getInstance().post(this);
}
start();
}
private void start() {
updateACCdnPath();
}
private void readACCdnPathFromFile() {
File file = new File(getFilesDir(), AC_CDN_PATH_FILENAME);
InputStream is = null;
try {
is = new FileInputStream(file);
String str = IOUtils.readString(is, "utf-8");
List<ACCdnPath> list = JSON.parseArray(str, ACCdnPath.class);
ACSite.getInstance().setCdnPath(list);
} catch (Exception e) {
e.printStackTrace();
} finally {
IOUtils.closeQuietly(is);
}
}
private void writeACCdnPathToFile(List<ACCdnPath> cdnPaths) {
File file = new File(getFilesDir(), AC_CDN_PATH_FILENAME);
OutputStream os = null;
try {
os = new FileOutputStream(file);
os.write(JSON.toJSONString(cdnPaths).getBytes("utf-8"));
} catch (IOException e) {
e.printStackTrace();
} finally {
IOUtils.closeQuietly(os);
}
}
private void updateACCdnPath() {
// First read cdn path from file
readACCdnPathFromFile();
NMBRequest request = new NMBRequest();
request.setSite(ACSite.getInstance());
request.setMethod(NMBClient.METHOD_GET_CDN_PATH);
request.setCallback(new NMBClient.Callback<List<ACCdnPath>>(){
@Override
public void onSuccess(List<ACCdnPath> result) {
ACSite.getInstance().setCdnPath(result);
writeACCdnPathToFile(result);
}
@Override
public void onFailure(Exception e) {
e.printStackTrace();
}
@Override
public void onCancel() {
}
});
getNMBClient(this).execute(request);
}
private void update() throws PackageManager.NameNotFoundException {
int oldVersionCode = Settings.getVersionCode();
PackageInfo pi = getPackageManager().getPackageInfo(getPackageName(), PackageManager.GET_ACTIVITIES);
Settings.putVersionCode(pi.versionCode);
if (oldVersionCode < 6) {
updateCookies(this);
}
if (oldVersionCode < 14) {
Settings.putGuideListActivity(true);
}
if (oldVersionCode < 42) {
Settings.putSetAnalysis(false);
Settings.putAnalysis(false);
}
// Fix cookie lost when save to file in 1.2.29 and below
if (oldVersionCode < 44) {
NMBApplication.getSimpleCookieStore(this).fixLostCookiePath();
}
}
public static void updateCookies(Context context) {
SimpleCookieStore cookieStore = NMBApplication.getSimpleCookieStore(context);
URL url = ACSite.getInstance().getSiteUrl();
HttpCookieWithId hcwi = cookieStore.getCookie(url, "userId");
if (hcwi != null) {
HttpCookie oldCookie = hcwi.httpCookie;
cookieStore.remove(url, oldCookie);
HttpCookie newCookie = new HttpCookie("userhash", oldCookie.getValue());
newCookie.setComment(oldCookie.getComment());
newCookie.setCommentURL(oldCookie.getCommentURL());
newCookie.setDiscard(oldCookie.getDiscard());
newCookie.setDomain(oldCookie.getDomain());
newCookie.setMaxAge(oldCookie.getMaxAge());
newCookie.setPath(oldCookie.getPath());
newCookie.setPortlist(oldCookie.getPortlist());
newCookie.setSecure(oldCookie.getSecure());
newCookie.setVersion(oldCookie.getVersion());
cookieStore.add(url, newCookie);
}
}
@Override
public void onTrimMemory(int level) {
super.onTrimMemory(level);
if (level == TRIM_MEMORY_BACKGROUND ) {
if (mConaco != null) {
mConaco.clearMemoryCache();
}
}
}
public static void updateNetworkState(Context context) {
((NMBApplication) context.getApplicationContext()).mConnectedWifi =
NetworkUtils.isConnectedWifi(context);
}
public static boolean isConnectedWifi(Context context) {
return ((NMBApplication) context.getApplicationContext()).mConnectedWifi;
}
public static SimpleCookieStore getSimpleCookieStore(@NonNull Context context) {
NMBApplication application = ((NMBApplication) context.getApplicationContext());
if (application.mSimpleCookieStore == null) {
application.mSimpleCookieStore = new SimpleCookieStore();
}
return application.mSimpleCookieStore;
}
@NonNull
public static NMBClient getNMBClient(@NonNull Context context) {
NMBApplication application = ((NMBApplication) context.getApplicationContext());
if (application.mNMBClient == null) {
application.mNMBClient = new NMBClient(application);
}
return application.mNMBClient;
}
private static int getMemoryCacheMaxSize(Context context) {
final ActivityManager activityManager = (ActivityManager) context.
getSystemService(Context.ACTIVITY_SERVICE);
return Math.min(20 * 1024 * 1024,
Math.round(0.2f * activityManager.getMemoryClass() * 1024 * 1024));
}
@NonNull
public static Conaco<ImageWrapper> getConaco(@NonNull Context context) {
NMBApplication application = ((NMBApplication) context.getApplicationContext());
if (application.mConaco == null) {
Conaco.Builder<ImageWrapper> builder = new Conaco.Builder<>();
builder.hasMemoryCache = true;
builder.memoryCacheMaxSize = getMemoryCacheMaxSize(context);
builder.hasDiskCache = true;
builder.diskCacheDir = new File(context.getCacheDir(), "thumb");
builder.diskCacheMaxSize = 80 * 1024 * 1024; // 80MB
builder.okHttpClient = getOkHttpClient(context);
builder.objectHelper = getImageWrapperHelper(context);
application.mConaco = builder.build();
}
return application.mConaco;
}
@NonNull
public static ImageWrapperHelper getImageWrapperHelper(@NonNull Context context) {
NMBApplication application = ((NMBApplication) context.getApplicationContext());
if (application.mImageWrapperHelper == null) {
application.mImageWrapperHelper = new ImageWrapperHelper();
}
return application.mImageWrapperHelper;
}
public static OkHttpClient getOkHttpClient(@NonNull Context context) {
NMBApplication application = ((NMBApplication) context.getApplicationContext());
if (application.mOkHttpClient == null) {
application.mOkHttpClient = new OkHttpClient.Builder()
.connectTimeout(15, TimeUnit.SECONDS)
.readTimeout(15, TimeUnit.SECONDS)
.writeTimeout(15, TimeUnit.SECONDS)
.dns(new NMBDns())
.cookieJar(new CookieDBJar(getSimpleCookieStore(context)))
.addInterceptor(new NMBInterceptor())
.build();
}
return application.mOkHttpClient;
}
@Override
public void uncaughtException(Thread thread, Throwable ex) {
if (!handleException(ex) && mDefaultHandler != null) {
mDefaultHandler.uncaughtException(thread, ex);
}
android.os.Process.killProcess(android.os.Process.myPid());
System.exit(1);
}
private boolean handleException(Throwable ex) {
if (ex == null) {
return false;
}
try {
ex.printStackTrace();
Crash.saveCrashInfo2File(this, ex);
return true;
} catch (Throwable tr) {
return false;
}
}
@Override
public void onReceive(int id, Object obj) {
setTheme((Boolean) obj ? R.style.AppTheme_Dark : R.style.AppTheme);
}
@Override
public void run() {
Log.i(TAG, "Native " + FileUtils.humanReadableByteCount(Debug.getNativeHeapAllocatedSize(), false));
SimpleHandler.getInstance().postDelayed(this, 3000);
}
}