package jp.agentec.abook.abv.cl.helper;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.Thread.UncaughtExceptionHandler;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Date;
import java.util.Set;

import jp.agentec.abook.abv.bl.acms.client.AcmsClient;
import jp.agentec.abook.abv.bl.acms.client.json.AcmsMessageJSON;
import jp.agentec.abook.abv.bl.common.ABVEnvironment;
import jp.agentec.abook.abv.bl.common.CommonExecutor;
import jp.agentec.abook.abv.bl.common.Constant;
import jp.agentec.abook.abv.bl.common.db.SQLiteDatabase;
import jp.agentec.abook.abv.bl.common.exception.ABVExceptionCode;
import jp.agentec.abook.abv.bl.common.exception.AcmsException;
import jp.agentec.abook.abv.bl.common.exception.NetworkDisconnectedException;
import jp.agentec.abook.abv.bl.common.log.Logger;
import jp.agentec.abook.abv.bl.common.nw.PCNetworkAdapter;
import jp.agentec.abook.abv.bl.common.util.SecurityUtil;
import jp.agentec.abook.abv.bl.data.ABVDataCache;
import jp.agentec.abook.abv.bl.data.DBConnector;
import jp.agentec.abook.abv.bl.data.DatabaseExporter;
import jp.agentec.abook.abv.bl.data.dao.AbstractDao;
import jp.agentec.abook.abv.bl.download.ContentDownloader;
import jp.agentec.abook.abv.cl.util.PreferenceUtil;
import jp.agentec.abook.abv.cl.util.RawResourceUtil;
import jp.agentec.abook.abv.launcher.android.ABVApplication;
import jp.agentec.abook.abv.launcher.android.LogFileProvider;
import jp.agentec.abook.abv.launcher.android.R;
import jp.agentec.abook.abv.ui.common.appinfo.AppDefType.DefPrefKey;
import jp.agentec.abook.abv.ui.common.constant.ErrorCode;
import jp.agentec.abook.abv.ui.common.dialog.ABookAlertDialog;
import jp.agentec.abook.abv.ui.common.helper.ProgressDialogHelper;
import jp.agentec.abook.abv.ui.common.util.ABVToastUtil;
import jp.agentec.abook.abv.ui.common.util.AlertDialogUtil;
import jp.agentec.adf.util.ArrayUtil;
import jp.agentec.adf.util.DateTimeUtil;
import jp.agentec.adf.util.DateTimeUtil.DateUnit;
import jp.agentec.adf.util.FileUtil;
import jp.agentec.adf.util.StringUtil;
import jp.agentec.adf.util.TreeUtil;

import net.lingala.zip4j.exception.ZipException;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.os.Debug;
import android.os.Handler;
import android.widget.Toast;

public class ABVUncaughtExceptionHandler implements UncaughtExceptionHandler {
	private final String TAG = "ABVUncaughtExceptionHandler";

	private static ABVUncaughtExceptionHandler instance = new ABVUncaughtExceptionHandler();
	private final UncaughtExceptionHandler mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();
	private Context mContext;  
	protected ABVDataCache cache = ABVDataCache.getInstance();

	private String mLogPath;
	private String mLogZipPath;
	private String mLogDeviceInfoPath;
	private String logUncaughtErrorPath;
	private String logHProfPath;
	private String logUncaughtErrorEncPath;
	private String logDebugInfoPath;
	private String appLogPath;
	private String tracePath = "/data/anr/traces.txt"; // ANR（アプリが応答しない）情報がOSから出力される
	private int errorReportFlg;  

	private AlertDialog alertDialog;
	private Handler handler = new Handler();


	public static ABVUncaughtExceptionHandler getInstancer() {
		return instance;
	}

	private ABVUncaughtExceptionHandler() {
	}

	public void init(Context context) {  
		mContext = context;  
		mLogPath = ABVEnvironment.getInstance().cacheDirectory + "/" + ABVEnvironment.LogExportName;
		mLogZipPath = ABVEnvironment.getInstance().cacheDirectory + "/" + ABVEnvironment.LogZipExportName;
		mLogDeviceInfoPath = ABVEnvironment.getInstance().cacheDirectory + "/" + ABVEnvironment.LogDeviceInfoPath;
		logUncaughtErrorPath = ABVEnvironment.getInstance().cacheDirectory + "/" + ABVEnvironment.LogUncaughtErrorName;
		logDebugInfoPath = ABVEnvironment.getInstance().cacheDirectory + "/" + ABVEnvironment.LogDebugInfoName;
		logHProfPath = ABVEnvironment.getInstance().cacheDirectory + "/" + ABVEnvironment.LogHProfPath;
		logUncaughtErrorEncPath = ABVEnvironment.getInstance().cacheDirectory + "/" + ABVEnvironment.LogUncaughtErrorEncName;
		appLogPath = ABVEnvironment.getInstance().cacheDirectory + "/" + ABVEnvironment.AppLogPath;
		errorReportFlg = mContext.getResources().getInteger(R.integer.error_report_flg);
	}

	/** 
	 * キャッチされない例外によって指定されたスレッドが終了したときに呼び出されます 
	 * 例外スタックトレースの内容をファイルに出力します 
	 */  
	@Override
    public void uncaughtException(Thread thread, Throwable t) {
		PrintWriter pw = null;
		boolean restartOk = true;
		try {
			Logger.e(TAG, "uncaughtException", t);
			File f = new File(logUncaughtErrorPath);
			if (f.exists()) {
				 long lastModiflied = f.lastModified();
				 long now = System.currentTimeMillis();
				 if (now - lastModiflied < 15000) { // 15秒以内にまた起きた場合は、自動再起動を行わない
					 Logger.e(TAG, "last error is so near that we cancel restart. lastTime=%tT, diff=%dmsec", lastModiflied, (now - lastModiflied));
					 restartOk = false;
				 }
			}
			FileUtil.createParentDirectory(logUncaughtErrorPath);
			pw = new PrintWriter(logUncaughtErrorPath);  
			t.printStackTrace(pw);
			pw.print("\n\n" + getDebugInfo());
			if (mContext.getResources().getBoolean(R.bool.hprof)) {
				Debug.dumpHprofData(logHProfPath);
			}
		} catch (FileNotFoundException e) {
			Logger.e("ABVUncaughtExceptionHandler", "uncaughtException", e);
		} catch (IOException e) {
			Logger.e("ABVUncaughtExceptionHandler", "uncaughtException", e);
		} finally {  
			if (pw != null) {
				try {
					pw.close();
				} catch (Exception e2) {}
			}
		}
		
		mDefaultHandler.uncaughtException(thread, t);
	}

	private String getDebugInfo() {
		StringBuffer sb = new StringBuffer();
		sb.append("getNativeHeapAllocatedSize=" + Debug.getNativeHeapAllocatedSize() + "\n");
		sb.append("getNativeHeapFreeSize=" + Debug.getNativeHeapFreeSize() + "\n");
		sb.append("getNativeHeapSize=" + Debug.getNativeHeapSize() + "\n");
		sb.append("getThreadAllocCount=" + Debug.getThreadAllocCount() + "\n");
		sb.append("getThreadAllocSize=" + Debug.getThreadAllocSize() + "\n");
		sb.append("getThreadGcInvocationCount=" + Debug.getThreadGcInvocationCount() + "\n");
		sb.append("totalMemory=" + Runtime.getRuntime().totalMemory() + "\n");
		sb.append("freeMemory=" + Runtime.getRuntime().freeMemory() + "\n");
		sb.append("maxMemory=" + Runtime.getRuntime().maxMemory() + "\n");
		
		sb.append("\n[threads]\n");
		Set<Thread> threadSet = Thread.getAllStackTraces().keySet();
		for (Thread thread : threadSet.toArray(new Thread[threadSet.size()])) {
			sb.append("thread id=" + thread.getId() + "\n");
			sb.append("thread name=" + thread.getName() + "\n");
			for (StackTraceElement ste : thread.getStackTrace()) {
				sb.append(ste + "\n");
			}
			sb.append("\n");
		}
		
		sb.append("DownloaderInfo:" + ContentDownloader.getInstance().getDownloadMapForDebug() +  "\n");
		
		return sb.toString();
	}

	/** 
	 * 障害レポートファイルがある場合、ダイアローグを表示し、送信します。 
	 * @param act 
	 *
	 */  
	public void sendBugReport(final Activity act) {  
		final File bugFile = new File(logUncaughtErrorPath);  
		if (!bugFile.exists()) { //バグレポートがなければ以降の処理を行わない  
			return;  
		}

		//AlertDialogを表示
		ABookAlertDialog alert = AlertDialogUtil.createAlertDialog(act, R.string.bug_report);
		alert.setMessage(R.string.BUG_REPORT);  
		alert.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {  
			@Override  
			public void onClick(DialogInterface dialog, int which) {  
				alertDialog.dismiss();
				try {
					switch (errorReportFlg) {
					case 1: // acmsへ送信
						sendToAcms(act, bugFile);
						break;
					case 2: // メール送信（平文）
						sendMail(act, bugFile, false, false);
						bugFile.delete();
						break;
					case 3: // メール送信（暗号化）
						String password = SecurityUtil.getOutZipPassword(cache.getMemberInfo().loginId, ABVEnvironment.ZipPasswordPrefix);
						FileUtil.saveZip(logUncaughtErrorEncPath, password, bugFile.getAbsolutePath());
						sendMail(act, new File(logUncaughtErrorEncPath), false, true);
						bugFile.delete(); // 元ファイルを削除
						break;
					default:
						break;
					}
				} catch (Exception e) {
					Logger.e(TAG, "sendToAcms error.", e);
					ABVToastUtil.showMakeText(act, mContext.getResources().getString(ErrorCode.E107.resId), Toast.LENGTH_SHORT);
					return;
				}
			}});  
		alert.setNegativeButton(R.string.no, new DialogInterface.OnClickListener() {  
			@Override  
			public void onClick(DialogInterface dialog, int which) { // キャンセルした場合もファイルを削除
				bugFile.delete();
			}});
		alertDialog = alert;  
		alertDialog.show();
	}  

	/**
	 * CMSサーバへファイルアップロード
	 * 
	 * 保存先：
	 *  /mnt/nas1/S0001/C事業者ID/WORK/DEVICE_LOG/ログインID/yyyyMMddHHmmss_元のファイル名
	 * 
	 * @param sendfile
	 * @throws AcmsException
	 * @throws NetworkDisconnectedException
	 * @throws IOException
	 */
	private void sendToAcms(final Activity act, final File sendfile) {
		CommonExecutor.execute(new Runnable() {
			@Override
            public void run() {
				try {
                    String uid = null;
                    String ma = null;
                    if (ABVEnvironment.getInstance().deviceIdType == Constant.DeviceIdType.MAC_ADDRESS) {
                        ma = ABVEnvironment.getInstance().deviceId;
                    } else {
                        uid = ABVEnvironment.getInstance().deviceId;
                    }

					AcmsMessageJSON json = AcmsClient.getInstance(cache.getUrlPath(), new PCNetworkAdapter()).uploadLogFile(cache.getMemberInfo().sid, sendfile, mContext.getString(R.string.version_name), String.valueOf(ABVEnvironment.DeviceTypeId), uid, ma);
					Logger.d(TAG, " status=" + json.httpStatus + " message=" + StringUtil.join(",", json.errorMessage));
					if (json.httpStatus != 200) {
						throw new AcmsException(ABVExceptionCode.S_E_ACMS_0001, json);
					}
					sendfile.delete();
				} catch (Exception e) {
					Logger.e(TAG, "sendToAcms error", e);
					handler.post(new Runnable() {
						@Override
						public void run() {
							ABVToastUtil.showMakeText(act, mContext.getResources().getString(ErrorCode.E107.resId), Toast.LENGTH_SHORT);
						}
					});
				}
			}
		});
	}

	/** 
	 * バグレポートの内容をメールで送信します。 
	 * @param act
	 * @param bugfile
	 * @param isExportFile
	 * @param isAttached
	 * @throws IOException 
	 */  
	private void sendMail(Activity act, File bugfile, boolean isExportFile, boolean isAttached) throws IOException {  
		Intent intent = new Intent();
		intent.setAction(Intent.ACTION_SEND);  
		intent.putExtra(Intent.EXTRA_EMAIL, new String[]{mContext.getString(R.string.error_report_email)});   // 宛先を指定
		
		if (isExportFile) {
			intent.putExtra(Intent.EXTRA_SUBJECT, mContext.getString(R.string.sendlog_email_subject));
		}
		else {
			intent.putExtra(Intent.EXTRA_SUBJECT, mContext.getString(R.string.error_email_subject));
		}

		StringBuffer body = new StringBuffer();         // 本文を指定
		body.append(mContext.getString(isAttached? R.string.sendlog_email_text: R.string.sendlog_email_text_no_attachment));
		body.append("\n\n");
		body.append(getAppInfo(act));
		body.append("\n\n");
		body.append(mContext.getString(R.string.complementary));
		body.append("\n(                                                                                      )\n\n");
		body.append(getDeviceInfo());
		body.append("\n\n");

		if (isAttached) {
			intent.setType("application/zip");      // データタイプを指定("message/rfc822");
			intent.putExtra(Intent.EXTRA_TEXT, body.toString());
			Uri uri = Uri.parse("content://" + mContext.getApplicationContext().getPackageName() + ".logfileprovider/" + bugfile.getName());
			intent.putExtra(Intent.EXTRA_STREAM, uri);        // 添付ファイルを指定
//			intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); //　TODO: later この許可に関係なくアクセスできてしまう. android:export="false"にすると、この設定をしても拒否となる。EXTRA_STREAMには効かない模様。 
//			mContext.grantUriPermission("*", uri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
			LogFileProvider.expired = DateTimeUtil.add(new Date(), DateUnit.Minute, 10); // 暫定的に発行時から10秒以内ならアクセス許可する
		}
		else {
			intent.setType("text/html");
			//バグレポートの内容を読み込みます。  
			String content = FileUtil.readTextFile(bugfile.getAbsolutePath());
			//メールで送信します。  
			intent.putExtra(Intent.EXTRA_TEXT, body.toString() + "\n\n" + content);  
		}
		act.startActivityForResult(Intent.createChooser(intent, mContext.getString(R.string.chose_mail_soft)), 100);
	}

	private StringBuffer getAppInfo(Activity act) {
		StringBuffer body = new StringBuffer();
		body.append("loginId: ");
		body.append(cache.getMemberInfo().loginId);
		body.append("\n");
		body.append(mContext.getString(R.string.app_name_info));
		body.append(": ");
		body.append(mContext.getString(R.string.app_name));
		body.append("\n");
		body.append(mContext.getString(R.string.app_version_info));
		body.append(": ");
		body.append(mContext.getString(R.string.version_name) + "(" + RawResourceUtil.getGitHash(act) + ")");
		
		return body;
	}

	private StringBuffer getDeviceInfo() {
		StringBuffer body = new StringBuffer();
		body.append("===== device info [start] =====");
		body.append("\nBOARD: " + Build.BOARD);
		body.append("\nBOOTLOADER: " + Build.BOOTLOADER);    //Android 1.6未対応
		body.append("\nBRAND: " + Build.BRAND);

		//#28505 Android OS 4.x 端末で障害ログ EXPORT でアプリが異常終了する
		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && Build.SUPPORTED_ABIS != null) {
			body.append("\nSUPPORTED_ABIS: " + ArrayUtil.join(Build.SUPPORTED_ABIS, ","));
		} else {
			//noinspection deprecation(API21から非推奨になった。無視)
			body.append("\nCPU_ABI: " + Build.CPU_ABI);
			//noinspection deprecation(API21から非推奨になった。無視)
			body.append("\nCPU_ABI2: " + Build.CPU_ABI2);        //Android 1.6未対応
		}

		body.append("\nDEVICE: " + Build.DEVICE);
		body.append("\nDISPLAY: " + Build.DISPLAY);
		body.append("\nFINGERPRINT: " + Build.FINGERPRINT);
		body.append("\nHARDWARE: " + Build.HARDWARE);        //Android 1.6未対応
		body.append("\nHOST: " + Build.HOST);
		body.append("\nID: " + Build.ID);
		body.append("\nMANUFACTURER: " + Build.MANUFACTURER);
		body.append("\nMODEL: " + Build.MODEL);
		body.append("\nPRODUCT: " + Build.PRODUCT);
//		body.append("\nRADIO: " + Build.RADIO);              //Android 1.6未対応
		body.append("\nTAGS: " + Build.TAGS);
		body.append("\nTIME: " + Build.TIME);
		body.append("\nTYPE: " + Build.TYPE);
		body.append("\nUNKNOWN: " + Build.UNKNOWN);          //Android 1.6未対応
		body.append("\nUSER: " + Build.USER);
		body.append("\nVERSION.CODENAME: " + Build.VERSION.CODENAME);
		body.append("\nVERSION.INCREMENTAL: " + Build.VERSION.INCREMENTAL);
		body.append("\nVERSION.RELEASE: " + Build.VERSION.RELEASE);
//		body.append("\nVERSION.SDK: " + Build.VERSION.SDK);
		body.append("\nVERSION.SDK_INT: " + Build.VERSION.SDK_INT);
		body.append("\n===== device info [end] =====\n");
		
		return body;
	}

	/**
	 * ログをExportし送信する。
	 * 送信方法は、エラー送信と同じ設定に従う。
	 * 
	 * @param act
	 */
	public void showLogExportDialog(final Activity act) {
		ABookAlertDialog alert = AlertDialogUtil.createAlertDialog(act, R.string.export_log_title);
		alert.setMessage(R.string.export_log_message);  
		alert.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {  
			@Override  
			public void onClick(DialogInterface dialog, int which) {  
				try {
					ProgressDialogHelper.showProgressPopup(act,true);
					logExport(act);
					AbstractDao.getDao(AbstractDao.class).vacuum(); // vacuum処理を入れる
				} catch (Exception e) {
					Logger.e(TAG, "sendToAcms error.", e);
					ABVToastUtil.showMakeText(act, mContext.getResources().getString(ErrorCode.E107.resId), Toast.LENGTH_SHORT);
					ProgressDialogHelper.closeProgressPopup();
					return;
				}
				alertDialog.dismiss();
			}});  
		alert.setNegativeButton(R.string.no, new DialogInterface.OnClickListener() {  
			@Override  
			public void onClick(DialogInterface dialog, int which) {
				alertDialog.dismiss();
			}});
		alertDialog = alert;
		alertDialog.show();
	}

	private void logExport(final Activity act) {
		CommonExecutor.execute(new Runnable() {
			@Override
            public void run() {
				try {
					Thread.sleep(500);
					exportSend(act);
				} catch (Exception e) {
					Logger.e(TAG, "logExport error.", e);
					handler.post(new Runnable() {
						@Override
						public void run() {
							ABVToastUtil.showMakeText(act, mContext.getResources().getString(ErrorCode.E107.resId), Toast.LENGTH_SHORT);
						}
					});
				}
				ProgressDialogHelper.closeProgressPopup();
			}
		});
	}
	
	/**
	 * ログをExportし送信する。
	 * 
	 * @throws IOException
	 * @throws ZipException
	 * @throws NoSuchAlgorithmException
	 * @throws AcmsException
	 * @throws NetworkDisconnectedException
	 */
	private void exportSend(Activity act) throws IOException, ZipException, NoSuchAlgorithmException {

        makeLogFile(act);

		switch (errorReportFlg) {
		case 1: // acmsへ送信
			sendToAcms(act, new File(mLogZipPath));
			break;
		case 2: // メール送信
		case 3:
			sendMail(act, new File(mLogZipPath), true, true);
			break;
		}

        if (Logger.isDebugEnabled()) {
            try {
                DatabaseExporter.fileExport(mLogZipPath, "/sdcard/download/" +  ABVEnvironment.LogZipExportName);
            } catch (Exception e) {
                Logger.w(TAG, "[exportSend]: fail send to /sdcard/download/");
            }
        }
	}

    /**
     * ログファイルを作成し、CMS側へ送る
     * @param act Activity
     * @return boolean 成功可否
     */
	public boolean sendLogFileToAcms(final Activity act) {
        if (act == null) {
            Logger.i(TAG, "[sendLogFileToAcms]: Fail. Activity is null");
            return false;
        }
        boolean result = false;
        try {
            makeLogFile(act);
            sendToAcms(act, new File(mLogZipPath));
            result = true;
        } catch (IOException e) {
            Logger.w(TAG, "[sendLogFileToAcms]: IOException:" + e.getMessage());
        } catch (ZipException e) {
            Logger.w(TAG, "[sendLogFileToAcms]: ZipException:" + e.getMessage());
        } catch (NoSuchAlgorithmException e) {
            Logger.w(TAG, "[sendLogFileToAcms]: NoSuchAlgorithmException:" + e.getMessage());
        }
        return result;
    }

	private void makeLogFile(Activity act) throws IOException, ZipException, NoSuchAlgorithmException {
        FileUtil.delete(mLogPath); // 既存のファイルを削除
        FileUtil.delete(mLogZipPath);

        ArrayList<String> commandLine = new ArrayList<>();
        commandLine.add("logcat");
        commandLine.add("-d");
        commandLine.add("-v");
        commandLine.add("threadtime");

        FileUtil.createParentDirectory(mLogPath);
        FileUtil.saveProcessOut(Runtime.getRuntime().exec(commandLine.toArray(new String[commandLine.size()])), mLogPath);
        String password = null;
        if (errorReportFlg == 3) {
            password = SecurityUtil.getOutZipPassword(cache.getMemberInfo().loginId, ABVEnvironment.ZipPasswordPrefix);
        }

        FileUtil.createFile(mLogDeviceInfoPath, getAppInfo(act) + "\n\n" + getDeviceInfo().toString());
        FileUtil.createFile(logDebugInfoPath, getDebugInfo());

        ArrayList<String> fileList = new ArrayList<>();
        fileList.add(mLogPath);
        fileList.add(mLogDeviceInfoPath);
        fileList.add(logDebugInfoPath);

        if (FileUtil.hasSize(logUncaughtErrorPath)) {
            fileList.add(logUncaughtErrorPath);
        }
        if (FileUtil.hasSize(logHProfPath)) {
            fileList.add(logHProfPath);
        }
        if (FileUtil.exists(tracePath)) {
            try {
                if (FileUtil.readTextFile(tracePath) != null) {
                    fileList.add(tracePath);
                }
            } catch (Exception e) {
                Logger.w(TAG, "ANR file read failed. " + e.toString());
            }
        }
        String appLogPrefix = appLogPath.substring(0, appLogPath.indexOf("%s"));
        File[] files = FileUtil.getChildFiles(new File(FileUtil.getParentPath(appLogPath)));
        if (files != null) {
            for (File file : files) {
                if (file.length() > 0 && file.getAbsolutePath().startsWith(appLogPrefix)) {
                    fileList.add(file.getAbsolutePath());
                }
            }
        }

        SQLiteDatabase db = DBConnector.getInstance().getDatabase();
        String databasePath = db.getPath();
        // データベースファイルを追加
        fileList.add(databasePath);

        // アプリ内部のフォルダ構造やファイル一覧情報を作成
        String appRoot = new File(ABVEnvironment.getInstance().rootDirectory).getParent();
        String data = TreeUtil.getTreePrint(appRoot);
        String treeFile = ABVEnvironment.getInstance().rootDirectory + "/tree.txt";
        FileUtil.createFile(treeFile, data);

        if (FileUtil.exists(treeFile)) {
            fileList.add(treeFile);
        } else {
            Logger.w(TAG, "[exportSend]: fail make treeFile");
        }

        FileUtil.saveZip(mLogZipPath, password, fileList.toArray(new String[0]));
        FileUtil.delete(mLogPath); // 元ファイルは削除
    }

	public void onLowMemory() {
		PrintWriter pw = null;     
		try {
			pw = new PrintWriter(logUncaughtErrorPath);  
			pw.print("----------- onLowMemory ---------");
			pw.print("\n\n" + getDebugInfo());
			if (mContext.getResources().getBoolean(R.bool.hprof)) {
				Debug.dumpHprofData(logHProfPath);
			}
		} catch (Exception e) {
			Logger.e("ABVUncaughtExceptionHandler", "onLowMemory", e);
		} finally {  
			if (pw != null) {
				try {
					pw.close();
				} catch (Exception e2) {}
			}
		}
	}
}

